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 3c3a87945..d293a81d4 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 @@ -1,1395 +1,1401 @@ -/* - * 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.EventListener; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; -import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; -import com.jpexs.decompiler.flash.abc.avm2.AVM2Deobfuscation; -import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; -import com.jpexs.decompiler.flash.abc.avm2.instructions.executing.CallPropertyIns; -import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushStringIns; -import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; -import com.jpexs.decompiler.flash.abc.avm2.parser.script.ActionScript3Parser; -import com.jpexs.decompiler.flash.abc.types.ABCException; -import com.jpexs.decompiler.flash.abc.types.ClassInfo; -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.NamespaceSet; -import com.jpexs.decompiler.flash.abc.types.ScriptInfo; -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.abc.usages.ClassNameMultinameUsage; -import com.jpexs.decompiler.flash.abc.usages.ConstVarNameMultinameUsage; -import com.jpexs.decompiler.flash.abc.usages.ConstVarTypeMultinameUsage; -import com.jpexs.decompiler.flash.abc.usages.DefinitionUsage; -import com.jpexs.decompiler.flash.abc.usages.ExtendsMultinameUsage; -import com.jpexs.decompiler.flash.abc.usages.ImplementsMultinameUsage; -import com.jpexs.decompiler.flash.abc.usages.MethodBodyMultinameUsage; -import com.jpexs.decompiler.flash.abc.usages.MethodNameMultinameUsage; -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.dumpview.DumpInfo; -import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; -import com.jpexs.decompiler.flash.tags.ABCContainerTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.graph.CompilationException; -import com.jpexs.decompiler.graph.DottedChain; -import com.jpexs.helpers.utf8.Utf8PrintWriter; -import java.io.IOException; -import java.io.OutputStream; -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.TreeSet; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -public class ABC { - - public ABCVersion version = new ABCVersion(47, 16); - - public AVM2ConstantPool constants = new AVM2ConstantPool(); - - public List method_info = new ArrayList<>(); - - public List metadata_info = new ArrayList<>(); - - public List instance_info = new ArrayList<>(); - - public List class_info = new ArrayList<>(); - - public List script_info = new ArrayList<>(); - - public List bodies = new ArrayList<>(); - - private ABCMethodIndexing abcMethodIndexing; - - public static final int MINORwithDECIMAL = 17; - - protected Set listeners = new HashSet<>(); - - private static final Logger logger = Logger.getLogger(ABC.class.getName()); - - private AVM2Deobfuscation deobfuscation; - - @Internal - public ABCContainerTag parentTag; - - /* Map from multiname index of namespace value to namespace name**/ - private Map namespaceMap; - - public ABC(ABCContainerTag tag) { - this.parentTag = tag; - this.deobfuscation = null; - } - - public SWF getSwf() { - return parentTag.getSwf(); - } - - public List getAbcTags() { - return getSwf().getAbcList(); - } - - public int addMethodBody(MethodBody body) { - bodies.add(body); - abcMethodIndexing = null; - return bodies.size() - 1; - } - - public int addMethodInfo(MethodInfo mi) { - method_info.add(mi); - return method_info.size() - 1; - } - - public TraitMethodGetterSetter addMethod(int classId, String name, boolean isStatic) { - Multiname multiname = new Multiname(); - multiname.kind = Multiname.QNAME; - multiname.name_index = constants.getStringId(name, true); - multiname.namespace_index = constants.getNamespaceId(Namespace.KIND_PACKAGE, "", 0, true); - int multinameId = constants.getMultinameId(multiname, true); - - MethodInfo methodInfo = new MethodInfo(); - int methodInfoId = addMethodInfo(methodInfo); - MethodBody methodBody = new MethodBody(); - methodBody.method_info = methodInfoId; - addMethodBody(methodBody); - - TraitMethodGetterSetter trait = new TraitMethodGetterSetter(); - trait.name_index = multinameId; - trait.kindType = Trait.TRAIT_METHOD; - if (isStatic) { - trait.kindFlags = Trait.ATTR_Final; - } - - trait.method_info = methodInfoId; - if (isStatic) { - ClassInfo classInfo = class_info.get(classId); - classInfo.static_traits.addTrait(trait); - trait.disp_id = classInfo.getNextDispId(); - } else { - InstanceInfo instanceInfo = instance_info.get(classId); - instanceInfo.instance_traits.addTrait(trait); - } - - return trait; - } - - public void addEventListener(EventListener listener) { - listeners.add(listener); - } - - public void removeEventListener(EventListener listener) { - listeners.remove(listener); - } - - protected void informListeners(String event, Object data) { - for (EventListener listener : listeners) { - listener.handleEvent(event, data); - } - } - - public int removeTraps() throws InterruptedException { - int rem = 0; - for (int s = 0; s < script_info.size(); s++) { - rem += script_info.get(s).removeTraps(s, this, ""); - } - return rem; - } - - public int removeDeadCode() throws InterruptedException { - int rem = 0; - for (MethodBody body : bodies) { - rem += body.removeDeadCode(constants, null/*FIXME*/, method_info.get(body.method_info)); - } - return rem; - } - - public Set getNsStringUsages() { - Set ret = new HashSet<>(); - for (int n = 1; n < constants.getNamespaceCount(); n++) { - ret.add(constants.getNamespace(n).name_index); - } - return ret; - } - - public Set getStringUsages() { - Set ret = new HashSet<>(); - for (MethodBody body : bodies) { - for (AVM2Instruction ins : body.getCode().code) { - for (int i = 0; i < ins.definition.operands.length; i++) { - if (ins.definition.operands[i] == AVM2Code.DAT_STRING_INDEX) { - ret.add(ins.operands[i]); - } - } - } - } - return ret; - } - - private void setStringUsageType(Map ret, int strIndex, String usageType) { - if (ret.containsKey(strIndex)) { - if (!"name".equals(usageType)) { - if (!ret.get(strIndex).equals(usageType)) { - ret.put(strIndex, "name"); - } - } - } else { - ret.put(strIndex, usageType); - } - } - - private void getStringUsageTypes(Map ret, Traits traits, boolean classesOnly) { - for (Trait t : traits.traits) { - int strIndex = constants.getMultiname(t.name_index).name_index; - String usageType = ""; - if (t instanceof TraitClass) { - TraitClass tc = (TraitClass) t; - getStringUsageTypes(ret, class_info.get(tc.class_info).static_traits, classesOnly); - getStringUsageTypes(ret, instance_info.get(tc.class_info).instance_traits, classesOnly); - - if (instance_info.get(tc.class_info).name_index != 0) { - setStringUsageType(ret, constants.getMultiname(instance_info.get(tc.class_info).name_index).name_index, "class"); - } - if (instance_info.get(tc.class_info).super_index != 0) { - setStringUsageType(ret, constants.getMultiname(instance_info.get(tc.class_info).super_index).name_index, "class"); - } - - usageType = "class"; - } - if (t instanceof TraitMethodGetterSetter) { - TraitMethodGetterSetter tm = (TraitMethodGetterSetter) t; - usageType = "method"; - MethodBody body = findBody(tm.method_info); - if (body != null) { - getStringUsageTypes(ret, body.traits, classesOnly); - } - } - if (t instanceof TraitFunction) { - TraitFunction tf = (TraitFunction) t; - MethodBody body = findBody(tf.method_info); - if (body != null) { - getStringUsageTypes(ret, body.traits, classesOnly); - } - usageType = "function"; - } - if (t instanceof TraitSlotConst) { - TraitSlotConst ts = (TraitSlotConst) t; - if (ts.isVar()) { - usageType = "var"; - } - if (ts.isConst()) { - usageType = "const"; - } - } - if (usageType.equals("class") || (!classesOnly)) { - setStringUsageType(ret, strIndex, usageType); - } - } - } - - public void getStringUsageTypes(Map ret, boolean classesOnly) { - for (ScriptInfo script : script_info) { - getStringUsageTypes(ret, script.traits, classesOnly); - } - } - - public void renameMultiname(int multinameIndex, String newname) { - if (multinameIndex <= 0 || multinameIndex >= constants.getMultinameCount()) { - throw new IllegalArgumentException("Multiname with index " + multinameIndex + " does not exist"); - } - Set stringUsages = getStringUsages(); - Set namespaceUsages = getNsStringUsages(); - int strIndex = constants.getMultiname(multinameIndex).name_index; - if (stringUsages.contains(strIndex) || namespaceUsages.contains(strIndex)) { // name is used elsewhere as string literal - strIndex = constants.getStringId(newname, true); - constants.getMultiname(multinameIndex).name_index = strIndex; - } else { - constants.setString(strIndex, newname); - } - } - - public void deobfuscateIdentifiers(HashMap namesMap, RenameType renameType, boolean classesOnly) { - Set stringUsages = getStringUsages(); - Set namespaceUsages = getNsStringUsages(); - Map stringUsageTypes = new HashMap<>(); - informListeners("deobfuscate", "Getting usage types..."); - getStringUsageTypes(stringUsageTypes, classesOnly); - AVM2Deobfuscation deobfuscation = getDeobfuscation(); - for (int i = 0; i < instance_info.size(); i++) { - informListeners("deobfuscate", "class " + i + "/" + instance_info.size()); - InstanceInfo insti = instance_info.get(i); - if (insti.name_index != 0) { - constants.getMultiname(insti.name_index).name_index = deobfuscation.deobfuscateName(stringUsageTypes, stringUsages, namespaceUsages, namesMap, constants.getMultiname(insti.name_index).name_index, true, renameType); - if (constants.getMultiname(insti.name_index).namespace_index != 0) { - constants.getNamespace(constants.getMultiname(insti.name_index).namespace_index).name_index - = deobfuscation.deobfuscatePackageName(stringUsageTypes, stringUsages, namesMap, constants.getNamespace(constants.getMultiname(insti.name_index).namespace_index).name_index, renameType); - } - } - if (insti.super_index != 0) { - constants.getMultiname(insti.super_index).name_index = deobfuscation.deobfuscateName(stringUsageTypes, stringUsages, namespaceUsages, namesMap, constants.getMultiname(insti.super_index).name_index, true, renameType); - } - } - if (classesOnly) { - return; - } - for (int i = 1; i < constants.getMultinameCount(); i++) { - informListeners("deobfuscate", "name " + i + "/" + constants.getMultinameCount()); - constants.getMultiname(i).name_index = deobfuscation.deobfuscateName(stringUsageTypes, stringUsages, namespaceUsages, namesMap, constants.getMultiname(i).name_index, false, renameType); - } - for (int i = 1; i < constants.getNamespaceCount(); i++) { - informListeners("deobfuscate", "namespace " + i + "/" + constants.getNamespaceCount()); - if (constants.getNamespace(i).kind != Namespace.KIND_PACKAGE) { // only packages - continue; - } - constants.getNamespace(i).name_index = deobfuscation.deobfuscatePackageName(stringUsageTypes, stringUsages, namesMap, constants.getNamespace(i).name_index, renameType); - } - - // process reflection using getDefinitionByName too - for (MethodBody body : bodies) { - for (int ip = 0; ip < body.getCode().code.size(); ip++) { - if (body.getCode().code.get(ip).definition instanceof CallPropertyIns) { - int mIndex = body.getCode().code.get(ip).operands[0]; - if (mIndex > 0) { - Multiname m = constants.getMultiname(mIndex); - if (m.getNameWithNamespace(constants).toRawString().equals("flash.utils.getDefinitionByName")) { - if (ip > 0) { - if (body.getCode().code.get(ip - 1).definition instanceof PushStringIns) { - int strIndex = body.getCode().code.get(ip - 1).operands[0]; - String fullname = constants.getString(strIndex); - String pkg = ""; - String name = fullname; - if (fullname.contains(".")) { - pkg = fullname.substring(0, fullname.lastIndexOf('.')); - name = fullname.substring(fullname.lastIndexOf('.') + 1); - } - if (!pkg.isEmpty()) { - int pkgStrIndex = constants.getStringId(pkg, true); - pkgStrIndex = deobfuscation.deobfuscatePackageName(stringUsageTypes, stringUsages, namesMap, pkgStrIndex, renameType); - pkg = constants.getString(pkgStrIndex); - } - int nameStrIndex = constants.getStringId(name, true); - nameStrIndex = deobfuscation.deobfuscateName(stringUsageTypes, stringUsages, namespaceUsages, namesMap, nameStrIndex, true, renameType); - name = constants.getString(nameStrIndex); - String fullChanged = ""; - if (!pkg.isEmpty()) { - fullChanged = pkg + "."; - } - fullChanged += name; - strIndex = constants.getStringId(fullChanged, true); - body.getCode().code.get(ip - 1).operands[0] = strIndex; - } - } - } - } - } - } - } - } - - public boolean hasDecimalSupport() { - return version.minor >= MINORwithDECIMAL; - } - - public void setDecimalSupport(boolean val) { - if (val) { - if (version.minor != MINORwithDECIMAL) { - version.minor = MINORwithDECIMAL; - ((Tag) parentTag).setModified(true); - } - } else if (version.minor == MINORwithDECIMAL) { - version.minor = MINORwithDECIMAL - 1; - ((Tag) parentTag).setModified(true); - } - } - - private boolean minVersionCheck(int minMajor, int minMinor) { - return version.compareTo(new ABCVersion(minMajor, minMinor)) >= 0; - } - - public boolean hasFloatSupport() { - return minVersionCheck(47, 16); - } - - public void setFloatSupport(boolean val) { - if (val) { - if (version.major < 47) { - version.major = 47; - ((Tag) parentTag).setModified(true); - } - } else if (version.major > 46) { - version.major = 46; - ((Tag) parentTag).setModified(true); - } - } - - public boolean hasExceptionSupport() { - return version.compareTo(new ABCVersion(46, 15)) > 0; - } - - public ABC(ABCInputStream ais, SWF swf, ABCContainerTag tag) throws IOException { - this.parentTag = tag; - int minor_version = ais.readU16("minor_version"); - int major_version = ais.readU16("major_version"); - version = new ABCVersion(major_version, minor_version); - logger.log(Level.FINE, "ABC minor_version: {0}, major_version: {1}", new Object[]{minor_version, major_version}); - - ais.newDumpLevel("constant_pool", "cpool_info"); - - // constant integers - int constant_int_pool_count = ais.readU30("int_count"); - constants.ensureIntCapacity(constant_int_pool_count); - if (constant_int_pool_count > 1) { - ais.newDumpLevel("integers", "integer[]"); - for (int i = 1; i < constant_int_pool_count; i++) { // index 0 not used. Values 1..n-1 - constants.addInt(ais.readS32("int")); - } - ais.endDumpLevel(); - } - - // constant unsigned integers - int constant_uint_pool_count = ais.readU30("uint_count"); - constants.ensureUIntCapacity(constant_uint_pool_count); - if (constant_uint_pool_count > 1) { - ais.newDumpLevel("uintegers", "uinteger[]"); - for (int i = 1; i < constant_uint_pool_count; i++) { // index 0 not used. Values 1..n-1 - constants.addUInt(ais.readU32("uint")); - } - ais.endDumpLevel(); - } - - // constant double - int constant_double_pool_count = ais.readU30("double_count"); - constants.ensureDoubleCapacity(constant_double_pool_count); - if (constant_double_pool_count > 1) { - ais.newDumpLevel("doubles", "double[]"); - for (int i = 1; i < constant_double_pool_count; i++) { // index 0 not used. Values 1..n-1 - constants.addDouble(ais.readDouble("double")); - } - ais.endDumpLevel(); - } - - // constant decimal - if (hasDecimalSupport()) { - int constant_decimal_pool_count = ais.readU30("decimal_count"); - constants.ensureDecimalCapacity(constant_decimal_pool_count); - if (constant_decimal_pool_count > 1) { - ais.newDumpLevel("decimals", "decimal[]"); - for (int i = 1; i < constant_decimal_pool_count; i++) { // index 0 not used. Values 1..n-1 - constants.addDecimal(ais.readDecimal("decimal")); - } - ais.endDumpLevel(); - } - } - - if (hasFloatSupport()) { - // constant float - int constant_float_pool_count = ais.readU30("float_count"); - if (constant_float_pool_count > 1) { - ais.newDumpLevel("floats", "float[]"); - for (int i = 1; i < constant_float_pool_count; i++) { // index 0 not used. Values 1..n-1 - constants.addFloat(ais.readFloat("float")); - } - ais.endDumpLevel(); - } - // constant float4 - int constant_float4_pool_count = ais.readU30("float4_count"); - if (constant_float4_pool_count > 1) { - ais.newDumpLevel("floats4", "float4[]"); - for (int i = 1; i < constant_float4_pool_count; i++) { // index 0 not used. Values 1..n-1 - constants.addFloat4(ais.readFloat4("float4")); - } - ais.endDumpLevel(); - } - } - - // constant string - int constant_string_pool_count = ais.readU30("string_count"); - constants.ensureStringCapacity(constant_string_pool_count); - if (constant_string_pool_count > 1) { - ais.newDumpLevel("strings", "string[]"); - for (int i = 1; i < constant_string_pool_count; i++) { // index 0 not used. Values 1..n-1 - long pos = ais.getPosition(); - constants.addString(ais.readString("string")); - } - ais.endDumpLevel(); - } - - // constant namespace - int constant_namespace_pool_count = ais.readU30("namespace_count"); - constants.ensureNamespaceCapacity(constant_namespace_pool_count); - if (constant_namespace_pool_count > 1) { - ais.newDumpLevel("namespaces", "namespace[]"); - for (int i = 1; i < constant_namespace_pool_count; i++) { // index 0 not used. Values 1..n-1 - constants.addNamespace(ais.readNamespace("namespace")); - } - ais.endDumpLevel(); - } - - // constant namespace set - int constant_namespace_set_pool_count = ais.readU30("ns_set_count"); - constants.ensureNamespaceSetCapacity(constant_namespace_set_pool_count); - if (constant_namespace_set_pool_count > 1) { - ais.newDumpLevel("ns_sets", "ns_set[]"); - for (int i = 1; i < constant_namespace_set_pool_count; i++) { // index 0 not used. Values 1..n-1 - ais.newDumpLevel("ns_set_infos", "ns_set_info[]"); - constants.addNamespaceSet(new NamespaceSet()); - int namespace_count = ais.readU30("count"); - constants.getNamespaceSet(i).namespaces = new int[namespace_count]; - for (int j = 0; j < namespace_count; j++) { - constants.getNamespaceSet(i).namespaces[j] = ais.readU30("ns"); - } - ais.endDumpLevel(); - } - ais.endDumpLevel(); - } - - // constant multiname - int constant_multiname_pool_count = ais.readU30("multiname_count"); - constants.ensureMultinameCapacity(constant_multiname_pool_count); - if (constant_multiname_pool_count > 1) { - ais.newDumpLevel("multiname", "multinames[]"); - for (int i = 1; i < constant_multiname_pool_count; i++) { // index 0 not used. Values 1..n-1 - constants.addMultiname(ais.readMultiname("multiname")); - } - ais.endDumpLevel(); - } - - ais.endDumpLevel(); // cpool_info - - // method info - int methods_count = ais.readU30("methods_count"); - method_info = new ArrayList<>(methods_count); // MethodInfo[methods_count]; - for (int i = 0; i < methods_count; i++) { - method_info.add(ais.readMethodInfo("method")); - } - - // metadata info - int metadata_count = ais.readU30("metadata_count"); - metadata_info = new ArrayList<>(metadata_count); - for (int i = 0; i < metadata_count; i++) { - int name_index = ais.readU30("name_index"); - int values_count = ais.readU30("values_count"); - int[] keys = new int[values_count]; - for (int v = 0; v < values_count; v++) { - keys[v] = ais.readU30("key"); - } - int[] values = new int[values_count]; - for (int v = 0; v < values_count; v++) { - values[v] = ais.readU30("value"); - } - metadata_info.add(new MetadataInfo(name_index, keys, values)); - } - - int class_count = ais.readU30("class_count"); - instance_info = new ArrayList<>(class_count); - for (int i = 0; i < class_count; i++) { - instance_info.add(ais.readInstanceInfo("instance")); - } - class_info = new ArrayList<>(class_count); - for (int i = 0; i < class_count; i++) { - ais.newDumpLevel("class", "class_info"); - ClassInfo ci = new ClassInfo(null); // do not create Traits in constructor - ci.cinit_index = ais.readU30("cinit_index"); - ci.static_traits = ais.readTraits("static_traits"); - class_info.add(ci); - ais.endDumpLevel(); - } - int script_count = ais.readU30("script_count"); - script_info = new ArrayList<>(script_count); - for (int i = 0; i < script_count; i++) { - ais.newDumpLevel("script", "script_info"); - ScriptInfo si = new ScriptInfo(null); // do not create Traits in constructor - si.init_index = ais.readU30("init_index"); - si.traits = ais.readTraits("traits"); - script_info.add(si); - ais.endDumpLevel(); - si.setModified(false); - } - - int bodies_count = ais.readU30("bodies_count"); - bodies = new ArrayList<>(bodies_count); - for (int i = 0; i < bodies_count; i++) { - DumpInfo di = ais.dumpInfo; - ais.newDumpLevel("method_body", "method_body_info"); - MethodBody mb = new MethodBody(this, null, null, null); // do not create Traits in constructor - try { - mb.method_info = ais.readU30("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")); - int ex_count = ais.readU30("ex_count"); - mb.exceptions = new ABCException[ex_count]; - for (int j = 0; j < ex_count; j++) { - ABCException abce = new ABCException(); - abce.start = ais.readU30("start"); - abce.end = ais.readU30("end"); - abce.target = ais.readU30("target"); - abce.type_index = ais.readU30("type_index"); - if (hasExceptionSupport()) { - abce.name_index = ais.readU30("name_index"); - } else { - abce.name_index = 0; - } - mb.exceptions[j] = abce; - } - mb.traits = ais.readTraits("traits"); - bodies.add(mb); - ais.endDumpLevel(); - } catch (EndOfStreamException ex) { - logger.log(Level.SEVERE, "MethodBody reading: End of stream", ex); - ais.endDumpLevelUntil(di); - break; - } - - SWFDecompilerPlugin.fireMethodBodyParsed(this, mb, swf); - } - - getMethodIndexing(); - - /*for(int i=0;i()); - } catch (InterruptedException ex) { - Logger.getLogger(ABC.class.getName()).log(Level.SEVERE, null, ex); - } - System.out.println(""+t.toString()); - } - //System.exit(0);*/ - SWFDecompilerPlugin.fireAbcParsed(this, swf); - } - - public void saveToStream(OutputStream os) throws IOException { - ABCOutputStream aos = new ABCOutputStream(os); - aos.writeU16(version.minor); - aos.writeU16(version.major); - - aos.writeU30(constants.getIntCount()); - for (int i = 1; i < constants.getIntCount(); i++) { - aos.writeS32(constants.getInt(i)); - } - aos.writeU30(constants.getUIntCount()); - for (int i = 1; i < constants.getUIntCount(); i++) { - aos.writeU32(constants.getUInt(i)); - } - - aos.writeU30(constants.getDoubleCount()); - for (int i = 1; i < constants.getDoubleCount(); i++) { - aos.writeDouble(constants.getDouble(i)); - } - - if (hasDecimalSupport()) { - aos.writeU30(constants.getDecimalCount()); - for (int i = 1; i < constants.getDecimalCount(); i++) { - aos.writeDecimal(constants.getDecimal(i)); - } - } - if (hasFloatSupport()) { - aos.writeU30(constants.getFloatCount()); - for (int i = 1; i < constants.getFloatCount(); i++) { - aos.writeFloat(constants.getFloat(i)); - } - aos.writeU30(constants.getFloat4Count()); - for (int i = 1; i < constants.getFloat4Count(); i++) { - aos.writeFloat4(constants.getFloat4(i)); - } - } - - aos.writeU30(constants.getStringCount()); - for (int i = 1; i < constants.getStringCount(); i++) { - aos.writeString(constants.getString(i)); - } - - aos.writeU30(constants.getNamespaceCount()); - for (int i = 1; i < constants.getNamespaceCount(); i++) { - aos.writeNamespace(constants.getNamespace(i)); - } - - aos.writeU30(constants.getNamespaceSetCount()); - for (int i = 1; i < constants.getNamespaceSetCount(); i++) { - aos.writeU30(constants.getNamespaceSet(i).namespaces.length); - for (int j = 0; j < constants.getNamespaceSet(i).namespaces.length; j++) { - aos.writeU30(constants.getNamespaceSet(i).namespaces[j]); - } - } - - aos.writeU30(constants.getMultinameCount()); - for (int i = 1; i < constants.getMultinameCount(); i++) { - aos.writeMultiname(constants.getMultiname(i)); - } - - aos.writeU30(method_info.size()); - for (MethodInfo mi : method_info) { - aos.writeMethodInfo(mi); - } - - aos.writeU30(metadata_info.size()); - for (MetadataInfo mi : metadata_info) { - aos.writeU30(mi.name_index); - aos.writeU30(mi.values.length); - for (int j = 0; j < mi.values.length; j++) { - aos.writeU30(mi.keys[j]); - } - for (int j = 0; j < mi.values.length; j++) { - aos.writeU30(mi.values[j]); - } - } - - aos.writeU30(class_info.size()); - for (InstanceInfo ii : instance_info) { - aos.writeInstanceInfo(ii); - } - for (ClassInfo ci : class_info) { - aos.writeU30(ci.cinit_index); - aos.writeTraits(ci.static_traits); - } - aos.writeU30(script_info.size()); - for (ScriptInfo si : script_info) { - aos.writeU30(si.init_index); - aos.writeTraits(si.traits); - } - - aos.writeU30(bodies.size()); - for (MethodBody mb : bodies) { - aos.writeU30(mb.method_info); - aos.writeU30(mb.max_stack); - aos.writeU30(mb.max_regs); - aos.writeU30(mb.init_scope_depth); - aos.writeU30(mb.max_scope_depth); - byte[] codeBytes = mb.getCodeBytes(); - aos.writeU30(codeBytes.length); - aos.write(codeBytes); - aos.writeU30(mb.exceptions.length); - for (int j = 0; j < mb.exceptions.length; j++) { - aos.writeU30(mb.exceptions[j].start); - aos.writeU30(mb.exceptions[j].end); - aos.writeU30(mb.exceptions[j].target); - aos.writeU30(mb.exceptions[j].type_index); - aos.writeU30(mb.exceptions[j].name_index); - } - aos.writeTraits(mb.traits); - } - } - - public MethodBody findBody(MethodInfo methodInfo) { - return getMethodIndexing().findMethodBody(methodInfo); - } - - public MethodBody findBody(int methodInfo) { - return getMethodIndexing().findMethodBody(methodInfo); - } - - public int findBodyIndex(MethodInfo methodInfo) { - return getMethodIndexing().findMethodBodyIndex(methodInfo); - } - - public int findBodyIndex(int methodInfo) { - return getMethodIndexing().findMethodBodyIndex(methodInfo); - } - - public MethodBody findBodyClassInitializerByClass(String className) { - for (int i = 0; i < instance_info.size(); i++) { - if (className.equals(constants.getMultiname(instance_info.get(i).name_index).getName(constants, null, true))) { - MethodBody body = findBody(class_info.get(i).cinit_index); - if (body != null) { - return body; - } - } - } - - return null; - } - - public MethodBody findBodyInstanceInitializerByClass(String className) { - for (int i = 0; i < instance_info.size(); i++) { - if (className.equals(constants.getMultiname(instance_info.get(i).name_index).getName(constants, null, true))) { - MethodBody body = findBody(instance_info.get(i).iinit_index); - if (body != null) { - return body; - } - } - } - - return null; - } - - public MethodBody findBodyByClassAndName(String className, String methodName) { - for (int i = 0; i < instance_info.size(); i++) { - if (className.equals(constants.getMultiname(instance_info.get(i).name_index).getName(constants, null, true))) { - for (Trait t : instance_info.get(i).instance_traits.traits) { - if (t instanceof TraitMethodGetterSetter) { - TraitMethodGetterSetter t2 = (TraitMethodGetterSetter) t; - if (methodName.equals(t2.getName(this).getName(constants, null, true))) { - MethodBody body = findBody(t2.method_info); - if (body != null) { - return body; - } - } - } - } - - for (Trait t : class_info.get(i).static_traits.traits) { - if (t instanceof TraitMethodGetterSetter) { - TraitMethodGetterSetter t2 = (TraitMethodGetterSetter) t; - if (methodName.equals(t2.getName(this).getName(constants, null, true))) { - MethodBody body = findBody(t2.method_info); - if (body != null) { - return body; - } - } - } - } - //break; - } - } - - return null; - } - - public boolean isStaticTraitId(int classIndex, int traitId) { - if (traitId < class_info.get(classIndex).static_traits.traits.size()) { - return true; - } else if (traitId < class_info.get(classIndex).static_traits.traits.size() + instance_info.get(classIndex).instance_traits.traits.size()) { - return false; - } else { - return true; // Can be class or instance initializer - } - } - - public Trait findTraitByTraitId(int classIndex, int traitId) { - if (classIndex == -1) { - return null; - } - List staticTraits = class_info.get(classIndex).static_traits.traits; - if (traitId >= 0 && traitId < staticTraits.size()) { - return staticTraits.get(traitId); - } else { - List instanceTraits = instance_info.get(classIndex).instance_traits.traits; - if (traitId >= 0 && traitId < staticTraits.size() + instanceTraits.size()) { - traitId -= staticTraits.size(); - return instanceTraits.get(traitId); - } else { - return null; // Can be class or instance initializer - } - } - } - - public int findMethodIdByTraitId(int classIndex, int traitId) { - if (classIndex == -1) { - return -1; - } - List staticTraits = class_info.get(classIndex).static_traits.traits; - if (traitId < staticTraits.size()) { - if (staticTraits.get(traitId) instanceof TraitMethodGetterSetter) { - return ((TraitMethodGetterSetter) staticTraits.get(traitId)).method_info; - } else { - return -1; - } - } else { - List instanceTraits = instance_info.get(classIndex).instance_traits.traits; - if (traitId < staticTraits.size() + instanceTraits.size()) { - traitId -= staticTraits.size(); - if (instanceTraits.get(traitId) instanceof TraitMethodGetterSetter) { - return ((TraitMethodGetterSetter) instanceTraits.get(traitId)).method_info; - } else { - return -1; - } - } else { - traitId -= staticTraits.size() + instanceTraits.size(); - if (traitId == 0) { - return instance_info.get(classIndex).iinit_index; - } else if (traitId == 1) { - return class_info.get(classIndex).cinit_index; - } else { - return -1; - } - } - } - } - - private Map getNamespaceMap() { - if (namespaceMap == null) { - Map map = new HashMap<>(); - for (ScriptInfo si : script_info) { - for (Trait t : si.traits.traits) { - if (t instanceof TraitSlotConst) { - TraitSlotConst s = ((TraitSlotConst) t); - if (s.isNamespace()) { - String key = constants.getNamespace(s.value_index).getName(constants).toRawString(); // assume not null - DottedChain val = constants.getMultiname(s.name_index).getNameWithNamespace(constants); - map.put(key, val); - } - } - } - } - namespaceMap = map; - } - - return namespaceMap; - } - - private AVM2Deobfuscation getDeobfuscation() { - if (deobfuscation == null) { - deobfuscation = new AVM2Deobfuscation(getSwf(), constants); - } - - return deobfuscation; - } - - public final ABCMethodIndexing getMethodIndexing() { - if (abcMethodIndexing == null) { - abcMethodIndexing = new ABCMethodIndexing(this); - } - - return abcMethodIndexing; - } - - public DottedChain nsValueToName(String valueStr) { - if (valueStr == null) { - return DottedChain.EMPTY; - } - - if (getNamespaceMap().containsKey(valueStr)) { - return getNamespaceMap().get(valueStr); - } else { - DottedChain ns = getDeobfuscation().builtInNs(valueStr); - if (ns == null) { - return DottedChain.EMPTY; - } else { - return ns; - } - } - } - - public List getScriptPacks(String packagePrefix, List allAbcs) { - List ret = new ArrayList<>(); - for (int i = 0; i < script_info.size(); i++) { - ret.addAll(script_info.get(i).getPacks(this, i, packagePrefix, allAbcs)); - } - return ret; - } - - public void dump(OutputStream os) { - Utf8PrintWriter output; - output = new Utf8PrintWriter(os); - constants.dump(output); - for (int i = 0; i < method_info.size(); i++) { - output.println("MethodInfo[" + i + "]:" + method_info.get(i).toString(constants, new ArrayList<>())); - } - for (int i = 0; i < metadata_info.size(); i++) { - output.println("MetadataInfo[" + i + "]:" + metadata_info.get(i).toString(constants)); - } - for (int i = 0; i < instance_info.size(); i++) { - output.println("InstanceInfo[" + i + "]:" + instance_info.get(i).toString(this, new ArrayList<>())); - } - for (int i = 0; i < class_info.size(); i++) { - output.println("ClassInfo[" + i + "]:" + class_info.get(i).toString(this, new ArrayList<>())); - } - for (int i = 0; i < script_info.size(); i++) { - output.println("ScriptInfo[" + i + "]:" + script_info.get(i).toString(this, new ArrayList<>())); - } - for (int i = 0; i < bodies.size(); i++) { - output.println("MethodBody[" + i + "]:"); //+ bodies[i].toString(this, constants, method_info)); - } - } - - private void checkMultinameUsedInMethod(int multinameIndex, int methodInfo, List ret, int classIndex, int traitIndex, boolean isStatic, boolean isInitializer, Traits traits, int parentTraitIndex) { - for (int p = 0; p < method_info.get(methodInfo).param_types.length; p++) { - if (method_info.get(methodInfo).param_types[p] == multinameIndex) { - ret.add(new MethodParamsMultinameUsage(this, multinameIndex, classIndex, traitIndex, isStatic, isInitializer, traits, parentTraitIndex)); - break; - } - } - if (method_info.get(methodInfo).ret_type == multinameIndex) { - ret.add(new MethodReturnTypeMultinameUsage(this, multinameIndex, classIndex, traitIndex, isStatic, isInitializer, traits, parentTraitIndex)); - } - MethodBody body = findBody(methodInfo); - if (body != null) { - findMultinameUsageInTraits(body.traits, multinameIndex, isStatic, classIndex, ret, traitIndex); - for (ABCException e : body.exceptions) { - if ((e.name_index == multinameIndex) || (e.type_index == multinameIndex)) { - ret.add(new MethodBodyMultinameUsage(this, multinameIndex, classIndex, traitIndex, isStatic, isInitializer, traits, parentTraitIndex)); - return; - } - } - for (AVM2Instruction ins : body.getCode().code) { - for (int o = 0; o < ins.definition.operands.length; o++) { - if (ins.definition.operands[o] == AVM2Code.DAT_MULTINAME_INDEX) { - if (ins.operands[o] == multinameIndex) { - ret.add(new MethodBodyMultinameUsage(this, multinameIndex, classIndex, traitIndex, isStatic, isInitializer, traits, parentTraitIndex)); - return; - } - } - } - } - } - } - - private void findMultinameUsageInTraits(Traits traits, int multinameIndex, boolean isStatic, int classIndex, List ret, int parentTraitIndex) { - for (int t = 0; t < traits.traits.size(); t++) { - if (traits.traits.get(t) instanceof TraitSlotConst) { - TraitSlotConst tsc = (TraitSlotConst) traits.traits.get(t); - if (tsc.name_index == multinameIndex) { - ret.add(new ConstVarNameMultinameUsage(this, multinameIndex, classIndex, t, isStatic, traits, parentTraitIndex)); - } - if (tsc.type_index == multinameIndex) { - ret.add(new ConstVarTypeMultinameUsage(this, multinameIndex, classIndex, t, isStatic, traits, parentTraitIndex)); - } - } - if (traits.traits.get(t) instanceof TraitMethodGetterSetter) { - TraitMethodGetterSetter tmgs = (TraitMethodGetterSetter) traits.traits.get(t); - if (tmgs.name_index == multinameIndex) { - ret.add(new MethodNameMultinameUsage(this, multinameIndex, classIndex, t, isStatic, false, traits, parentTraitIndex)); - } - checkMultinameUsedInMethod(multinameIndex, tmgs.method_info, ret, classIndex, t, isStatic, false, traits, parentTraitIndex); - } - } - } - - public List findMultinameDefinition(int multinameIndex) { - List usages = findMultinameUsage(multinameIndex); - List ret = new ArrayList<>(); - for (MultinameUsage u : usages) { - if (u instanceof DefinitionUsage) { - ret.add(u); - } - } - return ret; - } - - public List findMultinameUsage(int multinameIndex) { - List ret = new ArrayList<>(); - if (multinameIndex == 0) { - return ret; - } - for (int c = 0; c < instance_info.size(); c++) { - if (instance_info.get(c).name_index == multinameIndex) { - ret.add(new ClassNameMultinameUsage(this, multinameIndex, c)); - } - if (instance_info.get(c).super_index == multinameIndex) { - ret.add(new ExtendsMultinameUsage(this, multinameIndex, c)); - } - for (int i = 0; i < instance_info.get(c).interfaces.length; i++) { - if (instance_info.get(c).interfaces[i] == multinameIndex) { - ret.add(new ImplementsMultinameUsage(this, multinameIndex, c)); - } - } - checkMultinameUsedInMethod(multinameIndex, instance_info.get(c).iinit_index, ret, c, 0, false, true, null, -1); - checkMultinameUsedInMethod(multinameIndex, class_info.get(c).cinit_index, ret, c, 0, true, true, null, -1); - findMultinameUsageInTraits(instance_info.get(c).instance_traits, multinameIndex, false, c, ret, -1); - findMultinameUsageInTraits(class_info.get(c).static_traits, multinameIndex, true, c, ret, -1); - } - loopm: - for (int m = 1; m < constants.getMultinameCount(); m++) { - if (constants.getMultiname(m).kind == Multiname.TYPENAME) { - if (constants.getMultiname(m).qname_index == multinameIndex) { - ret.add(new TypeNameMultinameUsage(this, m)); - continue; - } - for (int mp : constants.getMultiname(m).params) { - if (mp == multinameIndex) { - ret.add(new TypeNameMultinameUsage(this, m)); - continue loopm; - } - } - } - } - return ret; - } - - public int findMethodInfoByName(int classId, String methodName) { - if (classId > -1) { - for (Trait t : instance_info.get(classId).instance_traits.traits) { - if (t instanceof TraitMethodGetterSetter) { - if (t.getName(this).getName(constants, null, true).equals(methodName)) { - return ((TraitMethodGetterSetter) t).method_info; - } - } - } - } - return -1; - } - - public int findMethodBodyByName(int classId, String methodName) { - if (classId > -1) { - for (Trait t : instance_info.get(classId).instance_traits.traits) { - if (t instanceof TraitMethodGetterSetter) { - if (t.getName(this).getName(constants, null, true).equals(methodName)) { - return findBodyIndex(((TraitMethodGetterSetter) t).method_info); - } - } - } - } - return -1; - } - - public int findMethodBodyByName(String className, String methodName) { - int classId = findClassByName(className); - return findMethodBodyByName(classId, methodName); - } - - public int findClassByName(DottedChain name) { - String str = name == null ? null : name.toRawString(); - return findClassByName(str); - } - - public int findClassByName(String name) { - for (int c = 0; c < instance_info.size(); c++) { - DottedChain s = constants.getMultiname(instance_info.get(c).name_index).getNameWithNamespace(constants); - if (name.equals(s.toRawString())) { - return c; - } - } - return -1; - } - - public List findScriptPacksByPath(String name, List allAbcs) { - List ret = new ArrayList<>(); - List allPacks = getScriptPacks(null, allAbcs); // todo: honfika: use filter parameter - if (name.endsWith(".**") || name.equals("**") || name.endsWith(".++") || name.equals("++")) { - name = name.substring(0, name.length() - 2); - - for (ScriptPack en : allPacks) { - if (en.getClassPath().toString().startsWith(name)) { - ret.add(en); - } - } - } else if (name.endsWith(".*") || name.equals("*") || name.endsWith(".+") || name.equals("+")) { - name = name.substring(0, name.length() - 1); - for (ScriptPack en : allPacks) { - String classPathStr = en.getClassPath().toString(); - if (classPathStr.startsWith(name)) { - String rem = name.isEmpty() ? classPathStr : classPathStr.substring(name.length()); - if (!rem.contains(".")) { - ret.add(en); - } - } - } - } else { - ScriptPack p = findScriptPackByPath(name, allAbcs); - if (p != null) { - ret.add(p); - } - } - return ret; - - } - - public ScriptPack findScriptPackByPath(String name, List allAbcs) { - List packs = getScriptPacks(null, allAbcs); - for (ScriptPack en : packs) { - if (en.getClassPath().toString().equals(name)) { - return en; - } - } - return null; - } - - private void removeClassFromTraits(Traits traits, int index) { - for (Trait t : traits.traits) { - if (t instanceof TraitClass) { - TraitClass tc = (TraitClass) t; - removeClassFromTraits(instance_info.get(tc.class_info).instance_traits, index); - removeClassFromTraits(class_info.get(tc.class_info).static_traits, index); - if (tc.class_info > index) { - tc.class_info--; - } - } - } - } - - public void addClass(ClassInfo ci, InstanceInfo ii, int index) { - for (MethodBody b : bodies) { - for (AVM2Instruction ins : b.getCode().code) { - for (int i = 0; i < ins.definition.operands.length; i++) { - if (ins.definition.operands[i] == AVM2Code.DAT_CLASS_INDEX) { - if (ins.operands[i] >= index) { - ins.operands[i]++; - } - } - } - } - } - for (ScriptInfo si : script_info) { - addClassInTraits(si.traits, index); - } - for (MethodBody b : bodies) { - addClassInTraits(b.traits, index); - } - instance_info.add(index, ii); - class_info.add(index, ci); - } - - private void addClassInTraits(Traits traits, int index) { - for (Trait t : traits.traits) { - if (t instanceof TraitClass) { - TraitClass tc = (TraitClass) t; - addClassInTraits(instance_info.get(tc.class_info).instance_traits, index); - addClassInTraits(class_info.get(tc.class_info).static_traits, index); - if (tc.class_info >= index) { - tc.class_info++; - } - } - } - } - - public void removeClass(int index) { - for (MethodBody b : bodies) { - for (AVM2Instruction ins : b.getCode().code) { - for (int i = 0; i < ins.definition.operands.length; i++) { - if (ins.definition.operands[i] == AVM2Code.DAT_CLASS_INDEX) { - if (ins.operands[i] > index) { - ins.operands[i]--; - } - } - } - } - } - for (ScriptInfo si : script_info) { - removeClassFromTraits(si.traits, index); - } - for (MethodBody b : bodies) { - removeClassFromTraits(b.traits, index); - } - instance_info.remove(index); - class_info.remove(index); - } - - private void removeMethodFromTraits(Traits traits, int index) { - for (Trait t : traits.traits) { - if (t instanceof TraitClass) { - TraitClass tc = (TraitClass) t; - removeMethodFromTraits(instance_info.get(tc.class_info).instance_traits, index); - removeMethodFromTraits(class_info.get(tc.class_info).static_traits, index); - } - if (t instanceof TraitMethodGetterSetter) { - TraitMethodGetterSetter tmgs = (TraitMethodGetterSetter) t; - if (tmgs.method_info > index) { - tmgs.method_info--; - } - } - if (t instanceof TraitFunction) { - TraitFunction tf = (TraitFunction) t; - if (tf.method_info > index) { - tf.method_info--; - } - } - } - } - - public void removeMethod(int index) { - - int bindex = -1; - for (int b = 0; b < bodies.size(); b++) { - if (bodies.get(b).method_info == index) { - bodies.remove(b); - bindex = b; - b--; - } - } - - for (MethodBody b : bodies) { - if (b.method_info > index) { - b.method_info--; - } - for (AVM2Instruction ins : b.getCode().code) { - for (int i = 0; i < ins.definition.operands.length; i++) { - if (ins.definition.operands[i] == AVM2Code.DAT_METHOD_INDEX) { - if (ins.operands[i] > index) { - ins.operands[i]--; - } - } - } - } - removeMethodFromTraits(b.traits, index); - } - - for (int c = 0; c < instance_info.size(); c++) { - InstanceInfo ii = instance_info.get(c); - if (ii.iinit_index > index) { - ii.iinit_index--; - } - ClassInfo ci = class_info.get(c); - if (ci.cinit_index > index) { - ci.cinit_index--; - } - } - - for (ScriptInfo si : script_info) { - if (si.init_index > index) { - si.init_index--; - } - removeMethodFromTraits(si.traits, index); - } - - abcMethodIndexing = null; - - method_info.remove(index); - } - - public boolean replaceScriptPack(ScriptPack pack, String as) throws AVM2ParseException, CompilationException, IOException, InterruptedException { - 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; - - 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; - } - - } - 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; - } - - public void pack() { - for (int c = 0; c < instance_info.size(); c++) { - if (instance_info.get(c).deleted) { - removeClass(c); - c--; - } - } - for (int m = 0; m < method_info.size(); m++) { - if (method_info.get(m).deleted) { - removeMethod(m); - m--; - } - } - - getMethodIndexing(); - } -} +/* + * 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.EventListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Deobfuscation; +import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; +import com.jpexs.decompiler.flash.abc.avm2.instructions.executing.CallPropertyIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushStringIns; +import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; +import com.jpexs.decompiler.flash.abc.avm2.parser.script.ActionScript3Parser; +import com.jpexs.decompiler.flash.abc.types.ABCException; +import com.jpexs.decompiler.flash.abc.types.ClassInfo; +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.NamespaceSet; +import com.jpexs.decompiler.flash.abc.types.ScriptInfo; +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.abc.usages.ClassNameMultinameUsage; +import com.jpexs.decompiler.flash.abc.usages.ConstVarNameMultinameUsage; +import com.jpexs.decompiler.flash.abc.usages.ConstVarTypeMultinameUsage; +import com.jpexs.decompiler.flash.abc.usages.DefinitionUsage; +import com.jpexs.decompiler.flash.abc.usages.ExtendsMultinameUsage; +import com.jpexs.decompiler.flash.abc.usages.ImplementsMultinameUsage; +import com.jpexs.decompiler.flash.abc.usages.MethodBodyMultinameUsage; +import com.jpexs.decompiler.flash.abc.usages.MethodNameMultinameUsage; +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.dumpview.DumpInfo; +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; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.graph.CompilationException; +import com.jpexs.decompiler.graph.DottedChain; +import com.jpexs.helpers.utf8.Utf8PrintWriter; +import java.io.IOException; +import java.io.OutputStream; +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.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class ABC { + + public ABCVersion version = new ABCVersion(47, 16); + + public AVM2ConstantPool constants = new AVM2ConstantPool(); + + public List method_info = new ArrayList<>(); + + public List metadata_info = new ArrayList<>(); + + public List instance_info = new ArrayList<>(); + + public List class_info = new ArrayList<>(); + + public List script_info = new ArrayList<>(); + + public List bodies = new ArrayList<>(); + + private ABCMethodIndexing abcMethodIndexing; + + public static final int MINORwithDECIMAL = 17; + + protected Set listeners = new HashSet<>(); + + private static final Logger logger = Logger.getLogger(ABC.class.getName()); + + private AVM2Deobfuscation deobfuscation; + + @Internal + public ABCContainerTag parentTag; + + /* Map from multiname index of namespace value to namespace name**/ + private Map namespaceMap; + + public ABC(ABCContainerTag tag) { + this.parentTag = tag; + this.deobfuscation = null; + } + + public SWF getSwf() { + return parentTag.getSwf(); + } + + public List getAbcTags() { + return getSwf().getAbcList(); + } + + public int addMethodBody(MethodBody body) { + bodies.add(body); + abcMethodIndexing = null; + return bodies.size() - 1; + } + + public int addMethodInfo(MethodInfo mi) { + method_info.add(mi); + return method_info.size() - 1; + } + + public TraitMethodGetterSetter addMethod(int classId, String name, boolean isStatic) { + Multiname multiname = new Multiname(); + multiname.kind = Multiname.QNAME; + multiname.name_index = constants.getStringId(name, true); + multiname.namespace_index = constants.getNamespaceId(Namespace.KIND_PACKAGE, "", 0, true); + int multinameId = constants.getMultinameId(multiname, true); + + MethodInfo methodInfo = new MethodInfo(); + int methodInfoId = addMethodInfo(methodInfo); + MethodBody methodBody = new MethodBody(); + methodBody.method_info = methodInfoId; + addMethodBody(methodBody); + + TraitMethodGetterSetter trait = new TraitMethodGetterSetter(); + trait.name_index = multinameId; + trait.kindType = Trait.TRAIT_METHOD; + if (isStatic) { + trait.kindFlags = Trait.ATTR_Final; + } + + trait.method_info = methodInfoId; + if (isStatic) { + ClassInfo classInfo = class_info.get(classId); + classInfo.static_traits.addTrait(trait); + trait.disp_id = classInfo.getNextDispId(); + } else { + InstanceInfo instanceInfo = instance_info.get(classId); + instanceInfo.instance_traits.addTrait(trait); + } + + return trait; + } + + public void addEventListener(EventListener listener) { + listeners.add(listener); + } + + public void removeEventListener(EventListener listener) { + listeners.remove(listener); + } + + protected void informListeners(String event, Object data) { + for (EventListener listener : listeners) { + listener.handleEvent(event, data); + } + } + + public int removeTraps() throws InterruptedException { + int rem = 0; + for (int s = 0; s < script_info.size(); s++) { + rem += script_info.get(s).removeTraps(s, this, ""); + } + return rem; + } + + public int removeDeadCode() throws InterruptedException { + int rem = 0; + for (MethodBody body : bodies) { + rem += body.removeDeadCode(constants, null/*FIXME*/, method_info.get(body.method_info)); + } + return rem; + } + + public Set getNsStringUsages() { + Set ret = new HashSet<>(); + for (int n = 1; n < constants.getNamespaceCount(); n++) { + ret.add(constants.getNamespace(n).name_index); + } + return ret; + } + + public Set getStringUsages() { + Set ret = new HashSet<>(); + for (MethodBody body : bodies) { + for (AVM2Instruction ins : body.getCode().code) { + for (int i = 0; i < ins.definition.operands.length; i++) { + if (ins.definition.operands[i] == AVM2Code.DAT_STRING_INDEX) { + ret.add(ins.operands[i]); + } + } + } + } + return ret; + } + + private void setStringUsageType(Map ret, int strIndex, String usageType) { + if (ret.containsKey(strIndex)) { + if (!"name".equals(usageType)) { + if (!ret.get(strIndex).equals(usageType)) { + ret.put(strIndex, "name"); + } + } + } else { + ret.put(strIndex, usageType); + } + } + + private void getStringUsageTypes(Map ret, Traits traits, boolean classesOnly) { + for (Trait t : traits.traits) { + int strIndex = constants.getMultiname(t.name_index).name_index; + String usageType = ""; + if (t instanceof TraitClass) { + TraitClass tc = (TraitClass) t; + getStringUsageTypes(ret, class_info.get(tc.class_info).static_traits, classesOnly); + getStringUsageTypes(ret, instance_info.get(tc.class_info).instance_traits, classesOnly); + + if (instance_info.get(tc.class_info).name_index != 0) { + setStringUsageType(ret, constants.getMultiname(instance_info.get(tc.class_info).name_index).name_index, "class"); + } + if (instance_info.get(tc.class_info).super_index != 0) { + setStringUsageType(ret, constants.getMultiname(instance_info.get(tc.class_info).super_index).name_index, "class"); + } + + usageType = "class"; + } + if (t instanceof TraitMethodGetterSetter) { + TraitMethodGetterSetter tm = (TraitMethodGetterSetter) t; + usageType = "method"; + MethodBody body = findBody(tm.method_info); + if (body != null) { + getStringUsageTypes(ret, body.traits, classesOnly); + } + } + if (t instanceof TraitFunction) { + TraitFunction tf = (TraitFunction) t; + MethodBody body = findBody(tf.method_info); + if (body != null) { + getStringUsageTypes(ret, body.traits, classesOnly); + } + usageType = "function"; + } + if (t instanceof TraitSlotConst) { + TraitSlotConst ts = (TraitSlotConst) t; + if (ts.isVar()) { + usageType = "var"; + } + if (ts.isConst()) { + usageType = "const"; + } + } + if (usageType.equals("class") || (!classesOnly)) { + setStringUsageType(ret, strIndex, usageType); + } + } + } + + public void getStringUsageTypes(Map ret, boolean classesOnly) { + for (ScriptInfo script : script_info) { + getStringUsageTypes(ret, script.traits, classesOnly); + } + } + + public void renameMultiname(int multinameIndex, String newname) { + if (multinameIndex <= 0 || multinameIndex >= constants.getMultinameCount()) { + throw new IllegalArgumentException("Multiname with index " + multinameIndex + " does not exist"); + } + Set stringUsages = getStringUsages(); + Set namespaceUsages = getNsStringUsages(); + int strIndex = constants.getMultiname(multinameIndex).name_index; + if (stringUsages.contains(strIndex) || namespaceUsages.contains(strIndex)) { // name is used elsewhere as string literal + strIndex = constants.getStringId(newname, true); + constants.getMultiname(multinameIndex).name_index = strIndex; + } else { + constants.setString(strIndex, newname); + } + } + + public void deobfuscateIdentifiers(HashMap namesMap, RenameType renameType, boolean classesOnly) { + Set stringUsages = getStringUsages(); + Set namespaceUsages = getNsStringUsages(); + Map stringUsageTypes = new HashMap<>(); + informListeners("deobfuscate", "Getting usage types..."); + getStringUsageTypes(stringUsageTypes, classesOnly); + AVM2Deobfuscation deobfuscation = getDeobfuscation(); + for (int i = 0; i < instance_info.size(); i++) { + informListeners("deobfuscate", "class " + i + "/" + instance_info.size()); + InstanceInfo insti = instance_info.get(i); + if (insti.name_index != 0) { + constants.getMultiname(insti.name_index).name_index = deobfuscation.deobfuscateName(stringUsageTypes, stringUsages, namespaceUsages, namesMap, constants.getMultiname(insti.name_index).name_index, true, renameType); + if (constants.getMultiname(insti.name_index).namespace_index != 0) { + constants.getNamespace(constants.getMultiname(insti.name_index).namespace_index).name_index + = deobfuscation.deobfuscatePackageName(stringUsageTypes, stringUsages, namesMap, constants.getNamespace(constants.getMultiname(insti.name_index).namespace_index).name_index, renameType); + } + } + if (insti.super_index != 0) { + constants.getMultiname(insti.super_index).name_index = deobfuscation.deobfuscateName(stringUsageTypes, stringUsages, namespaceUsages, namesMap, constants.getMultiname(insti.super_index).name_index, true, renameType); + } + } + if (classesOnly) { + return; + } + for (int i = 1; i < constants.getMultinameCount(); i++) { + informListeners("deobfuscate", "name " + i + "/" + constants.getMultinameCount()); + constants.getMultiname(i).name_index = deobfuscation.deobfuscateName(stringUsageTypes, stringUsages, namespaceUsages, namesMap, constants.getMultiname(i).name_index, false, renameType); + } + for (int i = 1; i < constants.getNamespaceCount(); i++) { + informListeners("deobfuscate", "namespace " + i + "/" + constants.getNamespaceCount()); + if (constants.getNamespace(i).kind != Namespace.KIND_PACKAGE) { // only packages + continue; + } + constants.getNamespace(i).name_index = deobfuscation.deobfuscatePackageName(stringUsageTypes, stringUsages, namesMap, constants.getNamespace(i).name_index, renameType); + } + + // process reflection using getDefinitionByName too + for (MethodBody body : bodies) { + for (int ip = 0; ip < body.getCode().code.size(); ip++) { + if (body.getCode().code.get(ip).definition instanceof CallPropertyIns) { + int mIndex = body.getCode().code.get(ip).operands[0]; + if (mIndex > 0) { + Multiname m = constants.getMultiname(mIndex); + if (m.getNameWithNamespace(constants).toRawString().equals("flash.utils.getDefinitionByName")) { + if (ip > 0) { + if (body.getCode().code.get(ip - 1).definition instanceof PushStringIns) { + int strIndex = body.getCode().code.get(ip - 1).operands[0]; + String fullname = constants.getString(strIndex); + String pkg = ""; + String name = fullname; + if (fullname.contains(".")) { + pkg = fullname.substring(0, fullname.lastIndexOf('.')); + name = fullname.substring(fullname.lastIndexOf('.') + 1); + } + if (!pkg.isEmpty()) { + int pkgStrIndex = constants.getStringId(pkg, true); + pkgStrIndex = deobfuscation.deobfuscatePackageName(stringUsageTypes, stringUsages, namesMap, pkgStrIndex, renameType); + pkg = constants.getString(pkgStrIndex); + } + int nameStrIndex = constants.getStringId(name, true); + nameStrIndex = deobfuscation.deobfuscateName(stringUsageTypes, stringUsages, namespaceUsages, namesMap, nameStrIndex, true, renameType); + name = constants.getString(nameStrIndex); + String fullChanged = ""; + if (!pkg.isEmpty()) { + fullChanged = pkg + "."; + } + fullChanged += name; + strIndex = constants.getStringId(fullChanged, true); + body.getCode().code.get(ip - 1).operands[0] = strIndex; + } + } + } + } + } + } + } + } + + public boolean hasDecimalSupport() { + return version.minor >= MINORwithDECIMAL; + } + + public void setDecimalSupport(boolean val) { + if (val) { + if (version.minor != MINORwithDECIMAL) { + version.minor = MINORwithDECIMAL; + ((Tag) parentTag).setModified(true); + } + } else if (version.minor == MINORwithDECIMAL) { + version.minor = MINORwithDECIMAL - 1; + ((Tag) parentTag).setModified(true); + } + } + + private boolean minVersionCheck(int minMajor, int minMinor) { + return version.compareTo(new ABCVersion(minMajor, minMinor)) >= 0; + } + + public boolean hasFloatSupport() { + return minVersionCheck(47, 16); + } + + public void setFloatSupport(boolean val) { + if (val) { + if (version.major < 47) { + version.major = 47; + ((Tag) parentTag).setModified(true); + } + } else if (version.major > 46) { + version.major = 46; + ((Tag) parentTag).setModified(true); + } + } + + public boolean hasExceptionSupport() { + return version.compareTo(new ABCVersion(46, 15)) > 0; + } + + public ABC(ABCInputStream ais, SWF swf, ABCContainerTag tag) throws IOException { + this.parentTag = tag; + int minor_version = ais.readU16("minor_version"); + int major_version = ais.readU16("major_version"); + version = new ABCVersion(major_version, minor_version); + logger.log(Level.FINE, "ABC minor_version: {0}, major_version: {1}", new Object[]{minor_version, major_version}); + + ais.newDumpLevel("constant_pool", "cpool_info"); + + // constant integers + int constant_int_pool_count = ais.readU30("int_count"); + constants.ensureIntCapacity(constant_int_pool_count); + if (constant_int_pool_count > 1) { + ais.newDumpLevel("integers", "integer[]"); + for (int i = 1; i < constant_int_pool_count; i++) { // index 0 not used. Values 1..n-1 + constants.addInt(ais.readS32("int")); + } + ais.endDumpLevel(); + } + + // constant unsigned integers + int constant_uint_pool_count = ais.readU30("uint_count"); + constants.ensureUIntCapacity(constant_uint_pool_count); + if (constant_uint_pool_count > 1) { + ais.newDumpLevel("uintegers", "uinteger[]"); + for (int i = 1; i < constant_uint_pool_count; i++) { // index 0 not used. Values 1..n-1 + constants.addUInt(ais.readU32("uint")); + } + ais.endDumpLevel(); + } + + // constant double + int constant_double_pool_count = ais.readU30("double_count"); + constants.ensureDoubleCapacity(constant_double_pool_count); + if (constant_double_pool_count > 1) { + ais.newDumpLevel("doubles", "double[]"); + for (int i = 1; i < constant_double_pool_count; i++) { // index 0 not used. Values 1..n-1 + constants.addDouble(ais.readDouble("double")); + } + ais.endDumpLevel(); + } + + // constant decimal + if (hasDecimalSupport()) { + int constant_decimal_pool_count = ais.readU30("decimal_count"); + constants.ensureDecimalCapacity(constant_decimal_pool_count); + if (constant_decimal_pool_count > 1) { + ais.newDumpLevel("decimals", "decimal[]"); + for (int i = 1; i < constant_decimal_pool_count; i++) { // index 0 not used. Values 1..n-1 + constants.addDecimal(ais.readDecimal("decimal")); + } + ais.endDumpLevel(); + } + } + + if (hasFloatSupport()) { + // constant float + int constant_float_pool_count = ais.readU30("float_count"); + if (constant_float_pool_count > 1) { + ais.newDumpLevel("floats", "float[]"); + for (int i = 1; i < constant_float_pool_count; i++) { // index 0 not used. Values 1..n-1 + constants.addFloat(ais.readFloat("float")); + } + ais.endDumpLevel(); + } + // constant float4 + int constant_float4_pool_count = ais.readU30("float4_count"); + if (constant_float4_pool_count > 1) { + ais.newDumpLevel("floats4", "float4[]"); + for (int i = 1; i < constant_float4_pool_count; i++) { // index 0 not used. Values 1..n-1 + constants.addFloat4(ais.readFloat4("float4")); + } + ais.endDumpLevel(); + } + } + + // constant string + int constant_string_pool_count = ais.readU30("string_count"); + constants.ensureStringCapacity(constant_string_pool_count); + if (constant_string_pool_count > 1) { + ais.newDumpLevel("strings", "string[]"); + for (int i = 1; i < constant_string_pool_count; i++) { // index 0 not used. Values 1..n-1 + long pos = ais.getPosition(); + constants.addString(ais.readString("string")); + } + ais.endDumpLevel(); + } + + // constant namespace + int constant_namespace_pool_count = ais.readU30("namespace_count"); + constants.ensureNamespaceCapacity(constant_namespace_pool_count); + if (constant_namespace_pool_count > 1) { + ais.newDumpLevel("namespaces", "namespace[]"); + for (int i = 1; i < constant_namespace_pool_count; i++) { // index 0 not used. Values 1..n-1 + constants.addNamespace(ais.readNamespace("namespace")); + } + ais.endDumpLevel(); + } + + // constant namespace set + int constant_namespace_set_pool_count = ais.readU30("ns_set_count"); + constants.ensureNamespaceSetCapacity(constant_namespace_set_pool_count); + if (constant_namespace_set_pool_count > 1) { + ais.newDumpLevel("ns_sets", "ns_set[]"); + for (int i = 1; i < constant_namespace_set_pool_count; i++) { // index 0 not used. Values 1..n-1 + ais.newDumpLevel("ns_set_infos", "ns_set_info[]"); + constants.addNamespaceSet(new NamespaceSet()); + int namespace_count = ais.readU30("count"); + constants.getNamespaceSet(i).namespaces = new int[namespace_count]; + for (int j = 0; j < namespace_count; j++) { + constants.getNamespaceSet(i).namespaces[j] = ais.readU30("ns"); + } + ais.endDumpLevel(); + } + ais.endDumpLevel(); + } + + // constant multiname + int constant_multiname_pool_count = ais.readU30("multiname_count"); + constants.ensureMultinameCapacity(constant_multiname_pool_count); + if (constant_multiname_pool_count > 1) { + ais.newDumpLevel("multiname", "multinames[]"); + for (int i = 1; i < constant_multiname_pool_count; i++) { // index 0 not used. Values 1..n-1 + constants.addMultiname(ais.readMultiname("multiname")); + } + ais.endDumpLevel(); + } + + ais.endDumpLevel(); // cpool_info + + // method info + int methods_count = ais.readU30("methods_count"); + method_info = new ArrayList<>(methods_count); // MethodInfo[methods_count]; + for (int i = 0; i < methods_count; i++) { + method_info.add(ais.readMethodInfo("method")); + } + + // metadata info + int metadata_count = ais.readU30("metadata_count"); + metadata_info = new ArrayList<>(metadata_count); + for (int i = 0; i < metadata_count; i++) { + int name_index = ais.readU30("name_index"); + int values_count = ais.readU30("values_count"); + int[] keys = new int[values_count]; + for (int v = 0; v < values_count; v++) { + keys[v] = ais.readU30("key"); + } + int[] values = new int[values_count]; + for (int v = 0; v < values_count; v++) { + values[v] = ais.readU30("value"); + } + metadata_info.add(new MetadataInfo(name_index, keys, values)); + } + + int class_count = ais.readU30("class_count"); + instance_info = new ArrayList<>(class_count); + for (int i = 0; i < class_count; i++) { + instance_info.add(ais.readInstanceInfo("instance")); + } + class_info = new ArrayList<>(class_count); + for (int i = 0; i < class_count; i++) { + ais.newDumpLevel("class", "class_info"); + ClassInfo ci = new ClassInfo(null); // do not create Traits in constructor + ci.cinit_index = ais.readU30("cinit_index"); + ci.static_traits = ais.readTraits("static_traits"); + class_info.add(ci); + ais.endDumpLevel(); + } + int script_count = ais.readU30("script_count"); + script_info = new ArrayList<>(script_count); + for (int i = 0; i < script_count; i++) { + ais.newDumpLevel("script", "script_info"); + ScriptInfo si = new ScriptInfo(null); // do not create Traits in constructor + si.init_index = ais.readU30("init_index"); + si.traits = ais.readTraits("traits"); + script_info.add(si); + ais.endDumpLevel(); + si.setModified(false); + } + + int bodies_count = ais.readU30("bodies_count"); + bodies = new ArrayList<>(bodies_count); + for (int i = 0; i < bodies_count; i++) { + DumpInfo di = ais.dumpInfo; + 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", DumpInfoSpecialType.ABC_CODE)); + int ex_count = ais.readU30("ex_count"); + mb.exceptions = new ABCException[ex_count]; + for (int j = 0; j < ex_count; j++) { + ABCException abce = new ABCException(); + abce.start = ais.readU30("start"); + abce.end = ais.readU30("end"); + abce.target = ais.readU30("target"); + abce.type_index = ais.readU30("type_index"); + if (hasExceptionSupport()) { + abce.name_index = ais.readU30("name_index"); + } else { + abce.name_index = 0; + } + mb.exceptions[j] = abce; + } + mb.traits = ais.readTraits("traits"); + bodies.add(mb); + ais.endDumpLevel(); + } catch (EndOfStreamException ex) { + logger.log(Level.SEVERE, "MethodBody reading: End of stream", ex); + ais.endDumpLevelUntil(di); + break; + } + + SWFDecompilerPlugin.fireMethodBodyParsed(this, mb, swf); + } + + getMethodIndexing(); + + /*for(int i=0;i()); + } catch (InterruptedException ex) { + Logger.getLogger(ABC.class.getName()).log(Level.SEVERE, null, ex); + } + System.out.println(""+t.toString()); + } + //System.exit(0);*/ + SWFDecompilerPlugin.fireAbcParsed(this, swf); + } + + public void saveToStream(OutputStream os) throws IOException { + ABCOutputStream aos = new ABCOutputStream(os); + aos.writeU16(version.minor); + aos.writeU16(version.major); + + aos.writeU30(constants.getIntCount()); + for (int i = 1; i < constants.getIntCount(); i++) { + aos.writeS32(constants.getInt(i)); + } + aos.writeU30(constants.getUIntCount()); + for (int i = 1; i < constants.getUIntCount(); i++) { + aos.writeU32(constants.getUInt(i)); + } + + aos.writeU30(constants.getDoubleCount()); + for (int i = 1; i < constants.getDoubleCount(); i++) { + aos.writeDouble(constants.getDouble(i)); + } + + if (hasDecimalSupport()) { + aos.writeU30(constants.getDecimalCount()); + for (int i = 1; i < constants.getDecimalCount(); i++) { + aos.writeDecimal(constants.getDecimal(i)); + } + } + if (hasFloatSupport()) { + aos.writeU30(constants.getFloatCount()); + for (int i = 1; i < constants.getFloatCount(); i++) { + aos.writeFloat(constants.getFloat(i)); + } + aos.writeU30(constants.getFloat4Count()); + for (int i = 1; i < constants.getFloat4Count(); i++) { + aos.writeFloat4(constants.getFloat4(i)); + } + } + + aos.writeU30(constants.getStringCount()); + for (int i = 1; i < constants.getStringCount(); i++) { + aos.writeString(constants.getString(i)); + } + + aos.writeU30(constants.getNamespaceCount()); + for (int i = 1; i < constants.getNamespaceCount(); i++) { + aos.writeNamespace(constants.getNamespace(i)); + } + + aos.writeU30(constants.getNamespaceSetCount()); + for (int i = 1; i < constants.getNamespaceSetCount(); i++) { + aos.writeU30(constants.getNamespaceSet(i).namespaces.length); + for (int j = 0; j < constants.getNamespaceSet(i).namespaces.length; j++) { + aos.writeU30(constants.getNamespaceSet(i).namespaces[j]); + } + } + + aos.writeU30(constants.getMultinameCount()); + for (int i = 1; i < constants.getMultinameCount(); i++) { + aos.writeMultiname(constants.getMultiname(i)); + } + + aos.writeU30(method_info.size()); + for (MethodInfo mi : method_info) { + aos.writeMethodInfo(mi); + } + + aos.writeU30(metadata_info.size()); + for (MetadataInfo mi : metadata_info) { + aos.writeU30(mi.name_index); + aos.writeU30(mi.values.length); + for (int j = 0; j < mi.values.length; j++) { + aos.writeU30(mi.keys[j]); + } + for (int j = 0; j < mi.values.length; j++) { + aos.writeU30(mi.values[j]); + } + } + + aos.writeU30(class_info.size()); + for (InstanceInfo ii : instance_info) { + aos.writeInstanceInfo(ii); + } + for (ClassInfo ci : class_info) { + aos.writeU30(ci.cinit_index); + aos.writeTraits(ci.static_traits); + } + aos.writeU30(script_info.size()); + for (ScriptInfo si : script_info) { + aos.writeU30(si.init_index); + aos.writeTraits(si.traits); + } + + aos.writeU30(bodies.size()); + for (MethodBody mb : bodies) { + aos.writeU30(mb.method_info); + aos.writeU30(mb.max_stack); + aos.writeU30(mb.max_regs); + aos.writeU30(mb.init_scope_depth); + aos.writeU30(mb.max_scope_depth); + byte[] codeBytes = mb.getCodeBytes(); + aos.writeU30(codeBytes.length); + aos.write(codeBytes); + aos.writeU30(mb.exceptions.length); + for (int j = 0; j < mb.exceptions.length; j++) { + aos.writeU30(mb.exceptions[j].start); + aos.writeU30(mb.exceptions[j].end); + aos.writeU30(mb.exceptions[j].target); + aos.writeU30(mb.exceptions[j].type_index); + aos.writeU30(mb.exceptions[j].name_index); + } + aos.writeTraits(mb.traits); + } + } + + public MethodBody findBody(MethodInfo methodInfo) { + return getMethodIndexing().findMethodBody(methodInfo); + } + + public MethodBody findBody(int methodInfo) { + return getMethodIndexing().findMethodBody(methodInfo); + } + + public int findBodyIndex(MethodInfo methodInfo) { + return getMethodIndexing().findMethodBodyIndex(methodInfo); + } + + public int findBodyIndex(int methodInfo) { + return getMethodIndexing().findMethodBodyIndex(methodInfo); + } + + public MethodBody findBodyClassInitializerByClass(String className) { + for (int i = 0; i < instance_info.size(); i++) { + if (className.equals(constants.getMultiname(instance_info.get(i).name_index).getName(constants, null, true))) { + MethodBody body = findBody(class_info.get(i).cinit_index); + if (body != null) { + return body; + } + } + } + + return null; + } + + public MethodBody findBodyInstanceInitializerByClass(String className) { + for (int i = 0; i < instance_info.size(); i++) { + if (className.equals(constants.getMultiname(instance_info.get(i).name_index).getName(constants, null, true))) { + MethodBody body = findBody(instance_info.get(i).iinit_index); + if (body != null) { + return body; + } + } + } + + return null; + } + + public MethodBody findBodyByClassAndName(String className, String methodName) { + for (int i = 0; i < instance_info.size(); i++) { + if (className.equals(constants.getMultiname(instance_info.get(i).name_index).getName(constants, null, true))) { + for (Trait t : instance_info.get(i).instance_traits.traits) { + if (t instanceof TraitMethodGetterSetter) { + TraitMethodGetterSetter t2 = (TraitMethodGetterSetter) t; + if (methodName.equals(t2.getName(this).getName(constants, null, true))) { + MethodBody body = findBody(t2.method_info); + if (body != null) { + return body; + } + } + } + } + + for (Trait t : class_info.get(i).static_traits.traits) { + if (t instanceof TraitMethodGetterSetter) { + TraitMethodGetterSetter t2 = (TraitMethodGetterSetter) t; + if (methodName.equals(t2.getName(this).getName(constants, null, true))) { + MethodBody body = findBody(t2.method_info); + if (body != null) { + return body; + } + } + } + } + //break; + } + } + + return null; + } + + public boolean isStaticTraitId(int classIndex, int traitId) { + if (traitId < class_info.get(classIndex).static_traits.traits.size()) { + return true; + } else if (traitId < class_info.get(classIndex).static_traits.traits.size() + instance_info.get(classIndex).instance_traits.traits.size()) { + return false; + } else { + return true; // Can be class or instance initializer + } + } + + public Trait findTraitByTraitId(int classIndex, int traitId) { + if (classIndex == -1) { + return null; + } + List staticTraits = class_info.get(classIndex).static_traits.traits; + if (traitId >= 0 && traitId < staticTraits.size()) { + return staticTraits.get(traitId); + } else { + List instanceTraits = instance_info.get(classIndex).instance_traits.traits; + if (traitId >= 0 && traitId < staticTraits.size() + instanceTraits.size()) { + traitId -= staticTraits.size(); + return instanceTraits.get(traitId); + } else { + return null; // Can be class or instance initializer + } + } + } + + public int findMethodIdByTraitId(int classIndex, int traitId) { + if (classIndex == -1) { + return -1; + } + List staticTraits = class_info.get(classIndex).static_traits.traits; + if (traitId < staticTraits.size()) { + if (staticTraits.get(traitId) instanceof TraitMethodGetterSetter) { + return ((TraitMethodGetterSetter) staticTraits.get(traitId)).method_info; + } else { + return -1; + } + } else { + List instanceTraits = instance_info.get(classIndex).instance_traits.traits; + if (traitId < staticTraits.size() + instanceTraits.size()) { + traitId -= staticTraits.size(); + if (instanceTraits.get(traitId) instanceof TraitMethodGetterSetter) { + return ((TraitMethodGetterSetter) instanceTraits.get(traitId)).method_info; + } else { + return -1; + } + } else { + traitId -= staticTraits.size() + instanceTraits.size(); + if (traitId == 0) { + return instance_info.get(classIndex).iinit_index; + } else if (traitId == 1) { + return class_info.get(classIndex).cinit_index; + } else { + return -1; + } + } + } + } + + private Map getNamespaceMap() { + if (namespaceMap == null) { + Map map = new HashMap<>(); + for (ScriptInfo si : script_info) { + for (Trait t : si.traits.traits) { + if (t instanceof TraitSlotConst) { + TraitSlotConst s = ((TraitSlotConst) t); + if (s.isNamespace()) { + String key = constants.getNamespace(s.value_index).getName(constants).toRawString(); // assume not null + DottedChain val = constants.getMultiname(s.name_index).getNameWithNamespace(constants); + map.put(key, val); + } + } + } + } + namespaceMap = map; + } + + return namespaceMap; + } + + private AVM2Deobfuscation getDeobfuscation() { + if (deobfuscation == null) { + deobfuscation = new AVM2Deobfuscation(getSwf(), constants); + } + + return deobfuscation; + } + + public final ABCMethodIndexing getMethodIndexing() { + if (abcMethodIndexing == null) { + abcMethodIndexing = new ABCMethodIndexing(this); + } + + return abcMethodIndexing; + } + + public DottedChain nsValueToName(String valueStr) { + if (valueStr == null) { + return DottedChain.EMPTY; + } + + if (getNamespaceMap().containsKey(valueStr)) { + return getNamespaceMap().get(valueStr); + } else { + DottedChain ns = getDeobfuscation().builtInNs(valueStr); + if (ns == null) { + return DottedChain.EMPTY; + } else { + return ns; + } + } + } + + public List getScriptPacks(String packagePrefix, List allAbcs) { + List ret = new ArrayList<>(); + for (int i = 0; i < script_info.size(); i++) { + ret.addAll(script_info.get(i).getPacks(this, i, packagePrefix, allAbcs)); + } + return ret; + } + + public void dump(OutputStream os) { + Utf8PrintWriter output; + output = new Utf8PrintWriter(os); + constants.dump(output); + for (int i = 0; i < method_info.size(); i++) { + output.println("MethodInfo[" + i + "]:" + method_info.get(i).toString(constants, new ArrayList<>())); + } + for (int i = 0; i < metadata_info.size(); i++) { + output.println("MetadataInfo[" + i + "]:" + metadata_info.get(i).toString(constants)); + } + for (int i = 0; i < instance_info.size(); i++) { + output.println("InstanceInfo[" + i + "]:" + instance_info.get(i).toString(this, new ArrayList<>())); + } + for (int i = 0; i < class_info.size(); i++) { + output.println("ClassInfo[" + i + "]:" + class_info.get(i).toString(this, new ArrayList<>())); + } + for (int i = 0; i < script_info.size(); i++) { + output.println("ScriptInfo[" + i + "]:" + script_info.get(i).toString(this, new ArrayList<>())); + } + for (int i = 0; i < bodies.size(); i++) { + output.println("MethodBody[" + i + "]:"); //+ bodies[i].toString(this, constants, method_info)); + } + } + + private void checkMultinameUsedInMethod(int multinameIndex, int methodInfo, List ret, int classIndex, int traitIndex, boolean isStatic, boolean isInitializer, Traits traits, int parentTraitIndex) { + for (int p = 0; p < method_info.get(methodInfo).param_types.length; p++) { + if (method_info.get(methodInfo).param_types[p] == multinameIndex) { + ret.add(new MethodParamsMultinameUsage(this, multinameIndex, classIndex, traitIndex, isStatic, isInitializer, traits, parentTraitIndex)); + break; + } + } + if (method_info.get(methodInfo).ret_type == multinameIndex) { + ret.add(new MethodReturnTypeMultinameUsage(this, multinameIndex, classIndex, traitIndex, isStatic, isInitializer, traits, parentTraitIndex)); + } + MethodBody body = findBody(methodInfo); + if (body != null) { + findMultinameUsageInTraits(body.traits, multinameIndex, isStatic, classIndex, ret, traitIndex); + for (ABCException e : body.exceptions) { + if ((e.name_index == multinameIndex) || (e.type_index == multinameIndex)) { + ret.add(new MethodBodyMultinameUsage(this, multinameIndex, classIndex, traitIndex, isStatic, isInitializer, traits, parentTraitIndex)); + return; + } + } + for (AVM2Instruction ins : body.getCode().code) { + for (int o = 0; o < ins.definition.operands.length; o++) { + if (ins.definition.operands[o] == AVM2Code.DAT_MULTINAME_INDEX) { + if (ins.operands[o] == multinameIndex) { + ret.add(new MethodBodyMultinameUsage(this, multinameIndex, classIndex, traitIndex, isStatic, isInitializer, traits, parentTraitIndex)); + return; + } + } + } + } + } + } + + private void findMultinameUsageInTraits(Traits traits, int multinameIndex, boolean isStatic, int classIndex, List ret, int parentTraitIndex) { + for (int t = 0; t < traits.traits.size(); t++) { + if (traits.traits.get(t) instanceof TraitSlotConst) { + TraitSlotConst tsc = (TraitSlotConst) traits.traits.get(t); + if (tsc.name_index == multinameIndex) { + ret.add(new ConstVarNameMultinameUsage(this, multinameIndex, classIndex, t, isStatic, traits, parentTraitIndex)); + } + if (tsc.type_index == multinameIndex) { + ret.add(new ConstVarTypeMultinameUsage(this, multinameIndex, classIndex, t, isStatic, traits, parentTraitIndex)); + } + } + if (traits.traits.get(t) instanceof TraitMethodGetterSetter) { + TraitMethodGetterSetter tmgs = (TraitMethodGetterSetter) traits.traits.get(t); + if (tmgs.name_index == multinameIndex) { + ret.add(new MethodNameMultinameUsage(this, multinameIndex, classIndex, t, isStatic, false, traits, parentTraitIndex)); + } + checkMultinameUsedInMethod(multinameIndex, tmgs.method_info, ret, classIndex, t, isStatic, false, traits, parentTraitIndex); + } + } + } + + public List findMultinameDefinition(int multinameIndex) { + List usages = findMultinameUsage(multinameIndex); + List ret = new ArrayList<>(); + for (MultinameUsage u : usages) { + if (u instanceof DefinitionUsage) { + ret.add(u); + } + } + return ret; + } + + public List findMultinameUsage(int multinameIndex) { + List ret = new ArrayList<>(); + if (multinameIndex == 0) { + return ret; + } + for (int c = 0; c < instance_info.size(); c++) { + if (instance_info.get(c).name_index == multinameIndex) { + ret.add(new ClassNameMultinameUsage(this, multinameIndex, c)); + } + if (instance_info.get(c).super_index == multinameIndex) { + ret.add(new ExtendsMultinameUsage(this, multinameIndex, c)); + } + for (int i = 0; i < instance_info.get(c).interfaces.length; i++) { + if (instance_info.get(c).interfaces[i] == multinameIndex) { + ret.add(new ImplementsMultinameUsage(this, multinameIndex, c)); + } + } + checkMultinameUsedInMethod(multinameIndex, instance_info.get(c).iinit_index, ret, c, 0, false, true, null, -1); + checkMultinameUsedInMethod(multinameIndex, class_info.get(c).cinit_index, ret, c, 0, true, true, null, -1); + findMultinameUsageInTraits(instance_info.get(c).instance_traits, multinameIndex, false, c, ret, -1); + findMultinameUsageInTraits(class_info.get(c).static_traits, multinameIndex, true, c, ret, -1); + } + loopm: + for (int m = 1; m < constants.getMultinameCount(); m++) { + if (constants.getMultiname(m).kind == Multiname.TYPENAME) { + if (constants.getMultiname(m).qname_index == multinameIndex) { + ret.add(new TypeNameMultinameUsage(this, m)); + continue; + } + for (int mp : constants.getMultiname(m).params) { + if (mp == multinameIndex) { + ret.add(new TypeNameMultinameUsage(this, m)); + continue loopm; + } + } + } + } + return ret; + } + + public int findMethodInfoByName(int classId, String methodName) { + if (classId > -1) { + for (Trait t : instance_info.get(classId).instance_traits.traits) { + if (t instanceof TraitMethodGetterSetter) { + if (t.getName(this).getName(constants, null, true).equals(methodName)) { + return ((TraitMethodGetterSetter) t).method_info; + } + } + } + } + return -1; + } + + public int findMethodBodyByName(int classId, String methodName) { + if (classId > -1) { + for (Trait t : instance_info.get(classId).instance_traits.traits) { + if (t instanceof TraitMethodGetterSetter) { + if (t.getName(this).getName(constants, null, true).equals(methodName)) { + return findBodyIndex(((TraitMethodGetterSetter) t).method_info); + } + } + } + } + return -1; + } + + public int findMethodBodyByName(String className, String methodName) { + int classId = findClassByName(className); + return findMethodBodyByName(classId, methodName); + } + + public int findClassByName(DottedChain name) { + String str = name == null ? null : name.toRawString(); + return findClassByName(str); + } + + public int findClassByName(String name) { + for (int c = 0; c < instance_info.size(); c++) { + DottedChain s = constants.getMultiname(instance_info.get(c).name_index).getNameWithNamespace(constants); + if (name.equals(s.toRawString())) { + return c; + } + } + return -1; + } + + public List findScriptPacksByPath(String name, List allAbcs) { + List ret = new ArrayList<>(); + List allPacks = getScriptPacks(null, allAbcs); // todo: honfika: use filter parameter + if (name.endsWith(".**") || name.equals("**") || name.endsWith(".++") || name.equals("++")) { + name = name.substring(0, name.length() - 2); + + for (ScriptPack en : allPacks) { + if (en.getClassPath().toString().startsWith(name)) { + ret.add(en); + } + } + } else if (name.endsWith(".*") || name.equals("*") || name.endsWith(".+") || name.equals("+")) { + name = name.substring(0, name.length() - 1); + for (ScriptPack en : allPacks) { + String classPathStr = en.getClassPath().toString(); + if (classPathStr.startsWith(name)) { + String rem = name.isEmpty() ? classPathStr : classPathStr.substring(name.length()); + if (!rem.contains(".")) { + ret.add(en); + } + } + } + } else { + ScriptPack p = findScriptPackByPath(name, allAbcs); + if (p != null) { + ret.add(p); + } + } + return ret; + + } + + public ScriptPack findScriptPackByPath(String name, List allAbcs) { + List packs = getScriptPacks(null, allAbcs); + for (ScriptPack en : packs) { + if (en.getClassPath().toString().equals(name)) { + return en; + } + } + return null; + } + + private void removeClassFromTraits(Traits traits, int index) { + for (Trait t : traits.traits) { + if (t instanceof TraitClass) { + TraitClass tc = (TraitClass) t; + removeClassFromTraits(instance_info.get(tc.class_info).instance_traits, index); + removeClassFromTraits(class_info.get(tc.class_info).static_traits, index); + if (tc.class_info > index) { + tc.class_info--; + } + } + } + } + + public void addClass(ClassInfo ci, InstanceInfo ii, int index) { + for (MethodBody b : bodies) { + for (AVM2Instruction ins : b.getCode().code) { + for (int i = 0; i < ins.definition.operands.length; i++) { + if (ins.definition.operands[i] == AVM2Code.DAT_CLASS_INDEX) { + if (ins.operands[i] >= index) { + ins.operands[i]++; + } + } + } + } + } + for (ScriptInfo si : script_info) { + addClassInTraits(si.traits, index); + } + for (MethodBody b : bodies) { + addClassInTraits(b.traits, index); + } + instance_info.add(index, ii); + class_info.add(index, ci); + } + + private void addClassInTraits(Traits traits, int index) { + for (Trait t : traits.traits) { + if (t instanceof TraitClass) { + TraitClass tc = (TraitClass) t; + addClassInTraits(instance_info.get(tc.class_info).instance_traits, index); + addClassInTraits(class_info.get(tc.class_info).static_traits, index); + if (tc.class_info >= index) { + tc.class_info++; + } + } + } + } + + public void removeClass(int index) { + for (MethodBody b : bodies) { + for (AVM2Instruction ins : b.getCode().code) { + for (int i = 0; i < ins.definition.operands.length; i++) { + if (ins.definition.operands[i] == AVM2Code.DAT_CLASS_INDEX) { + if (ins.operands[i] > index) { + ins.operands[i]--; + } + } + } + } + } + for (ScriptInfo si : script_info) { + removeClassFromTraits(si.traits, index); + } + for (MethodBody b : bodies) { + removeClassFromTraits(b.traits, index); + } + instance_info.remove(index); + class_info.remove(index); + } + + private void removeMethodFromTraits(Traits traits, int index) { + for (Trait t : traits.traits) { + if (t instanceof TraitClass) { + TraitClass tc = (TraitClass) t; + removeMethodFromTraits(instance_info.get(tc.class_info).instance_traits, index); + removeMethodFromTraits(class_info.get(tc.class_info).static_traits, index); + } + if (t instanceof TraitMethodGetterSetter) { + TraitMethodGetterSetter tmgs = (TraitMethodGetterSetter) t; + if (tmgs.method_info > index) { + tmgs.method_info--; + } + } + if (t instanceof TraitFunction) { + TraitFunction tf = (TraitFunction) t; + if (tf.method_info > index) { + tf.method_info--; + } + } + } + } + + public void removeMethod(int index) { + + int bindex = -1; + for (int b = 0; b < bodies.size(); b++) { + if (bodies.get(b).method_info == index) { + bodies.remove(b); + bindex = b; + b--; + } + } + + for (MethodBody b : bodies) { + if (b.method_info > index) { + b.method_info--; + } + for (AVM2Instruction ins : b.getCode().code) { + for (int i = 0; i < ins.definition.operands.length; i++) { + if (ins.definition.operands[i] == AVM2Code.DAT_METHOD_INDEX) { + if (ins.operands[i] > index) { + ins.operands[i]--; + } + } + } + } + removeMethodFromTraits(b.traits, index); + } + + for (int c = 0; c < instance_info.size(); c++) { + InstanceInfo ii = instance_info.get(c); + if (ii.iinit_index > index) { + ii.iinit_index--; + } + ClassInfo ci = class_info.get(c); + if (ci.cinit_index > index) { + ci.cinit_index--; + } + } + + for (ScriptInfo si : script_info) { + if (si.init_index > index) { + si.init_index--; + } + removeMethodFromTraits(si.traits, index); + } + + abcMethodIndexing = null; + + method_info.remove(index); + } + + public boolean replaceScriptPack(ScriptPack pack, String as) throws AVM2ParseException, CompilationException, IOException, InterruptedException { + 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; + + 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; + } + + } + 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; + } + + public void pack() { + for (int c = 0; c < instance_info.size(); c++) { + if (instance_info.get(c).deleted) { + removeClass(c); + c--; + } + } + for (int m = 0; m < method_info.size(); m++) { + if (method_info.get(m).deleted) { + removeMethod(m); + m--; + } + } + + getMethodIndexing(); + } +} 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 e4189d543..ac413ff39 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..a9f721bf5 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 @@ -1,356 +1,356 @@ -package com.jpexs.decompiler.flash.docs; - -import com.jpexs.decompiler.flash.ApplicationInfo; -import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; -import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2InstructionFlag; -import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition; -import com.jpexs.helpers.Cache; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.utf8.Utf8Helper; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.ResourceBundle; -import java.util.Set; -import java.util.TimeZone; -import java.util.TreeSet; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Generator for AVM2 instruction set documentation. - * - * @author JPEXS - */ -public class As3PCodeDocs { - - private static ResourceBundle prop; - - private static Map flagDescriptions = new HashMap<>(); - - private static Cache docsCache = Cache.getInstance(false, true, "as3DocsCache"); - - private static Map nameToDef = new HashMap<>(); - - static final String NEWLINE = "\r\n"; - - static { - prop = ResourceBundle.getBundle("com.jpexs.decompiler.flash.locales.docs.pcode.AS3"); - for (InstructionDefinition def : AVM2Code.allInstructionSet) { - if (def == null) { - continue; - } - nameToDef.put(def.instructionName, def); - } - - for (AVM2InstructionFlag flg : AVM2InstructionFlag.values()) { - String flagIdent = makeIdent(flg.toString()); - String flagDescription = getProperty("instructionFlag." + flagIdent); - flagDescriptions.put(flg, flagDescription); - } - } - - private static String makeIdent(String name) { - StringBuilder identName = new StringBuilder(); - boolean cap = false; - for (int i = 0; i < name.length(); i++) { - char c = name.charAt(i); - if (c == '_') { - cap = true; - continue; - } - if (cap) { - identName.append(c); - cap = false; - } else { - identName.append(Character.toLowerCase(c)); - } - } - return identName.toString(); - } - - public static String getDocsForIns(String insName, boolean showDataSize, boolean ui, boolean withStyle) { - if (!nameToDef.containsKey(insName)) { - return null; - } - return getDocsForIns(nameToDef.get(insName), showDataSize, ui, withStyle); - } - - private static String getProperty(String name) { - if (prop.containsKey(name)) { - return Helper.escapeHTML(prop.getString(name)); - } - return null; - } - - private static String htmlFooter() { - StringBuilder sb = new StringBuilder(); - - sb.append(""); - return sb.toString(); - } - - private static String meta(String name, String content) { - return "\t\t" + NEWLINE; - } - - private static String metaProp(String name, String content) { - return "\t\t" + NEWLINE; - } - - private static String meta(String name, Date content) { - return "\t\t" + NEWLINE; - } - - private static String htmlHeader(String js, String style) { - Date dateGenerated = new Date(); - StringBuilder sb = new StringBuilder(); - sb.append("").append(NEWLINE). - append("").append(NEWLINE). - append("\t").append(NEWLINE); - if (style != null && !style.isEmpty()) { - sb.append("\t\t").append(NEWLINE); - } - if (js != null && !js.isEmpty()) { - sb.append("\t\t").append(NEWLINE); - } - sb.append("\t\t").append(NEWLINE). - append(meta("generator", ApplicationInfo.applicationVerName)). - append(meta("description", getProperty("ui.list.pageDescription"))). - append(metaProp("og:title", getProperty("ui.list.pageTitle"))). - append(metaProp("og:type", "article")). - append(metaProp("og:description", getProperty("ui.list.pageDescription"))). - append(meta("date", dateGenerated)). - append("\t\t").append(getProperty("ui.list.documentTitle")).append("").append(NEWLINE). - append("\t").append(NEWLINE); - return sb.toString(); - } - - public static String getDocsForIns(InstructionDefinition def, boolean showDataSize, boolean ui, boolean standalone) { - final String cacheKey = def.instructionName + "|" + (showDataSize ? 1 : 0) + "|" + (ui ? 1 : 0) + "|" + (standalone ? 1 : 0); - String v = docsCache.get(cacheKey); - if (v != null) { - return v; - } - - StringBuilder sb = new StringBuilder(); - if (standalone) { - sb.append(htmlHeader("", getStyle())); - } - String insName = def.instructionName; - - String insShortDescription = getProperty("instruction." + insName + ".shortDescription"); - String insDescription = getProperty("instruction." + insName + ".description"); - String stackBefore = def.hasFlag(AVM2InstructionFlag.UNKNOWN_STACK) ? "" : getProperty("instruction." + insName + ".stackBefore"); - String stackAfter = def.hasFlag(AVM2InstructionFlag.UNKNOWN_STACK) ? "" : getProperty("instruction." + insName + ".stackAfter"); - if (stackBefore.trim().isEmpty()) { - stackBefore = getProperty("ui.stack.before.empty"); - } else { - stackBefore = getProperty("ui.stack.before") + stackBefore; - } - if (stackAfter.trim().isEmpty()) { - stackAfter = getProperty("ui.stack.before.empty"); - } else { - stackAfter = getProperty("ui.stack.before") + stackAfter; - } - stackBefore = "" + stackBefore + ""; - stackAfter = "" + stackAfter + ""; - - String stack = def.hasFlag(AVM2InstructionFlag.UNKNOWN_STACK) ? getProperty("ui.unknown") : stackBefore + "" + getProperty("ui.stack.to") + "" + stackAfter; - String operandsDoc = def.hasFlag(AVM2InstructionFlag.UNKNOWN_OPERANDS) ? getProperty("ui.unknown") : getProperty("instruction." + insName + ".operands"); - - sb.append("<"); - sb.append(standalone ? "body" : "div"); - sb.append(" class=\"instruction"); - - for (AVM2InstructionFlag fl : def.flags) { - sb.append(" instruction-flag-").append(makeIdent(fl.toString())); - } - sb.append("\">"); - - sb.append("
").append(String.format("0x%02X", def.instructionCode)).append(" ").append(insName).append(""); - - if (def.hasFlag(AVM2InstructionFlag.UNKNOWN_OPERANDS)) { - sb.append(" ").append(getProperty("ui.unknown")).append(NEWLINE); - } else { - String[] operandsDocs = operandsDoc.split(", ?"); - boolean first = true; - if (def.operands.length > 0) { - sb.append(" "); - } - for (int i = 0; i < def.operands.length; i++) { - int op = def.operands[i]; - String opDoc = operandsDocs[i]; - String operandTypeRaw = AVM2Code.operandTypeToString(op, false); - String operandTypeCombined = AVM2Code.operandTypeToString(op, true); - if (operandTypeCombined.contains(", ")) { - String operandTypesCombined[] = operandTypeCombined.split(", ?"); - String operandTypesRaw[] = operandTypeRaw.split(", ?"); - - for (int j = 0; j < operandTypesCombined.length; j++) { - if (!first) { - sb.append(", "); - } else { - first = false; - } - opDoc = operandsDocs[i + j]; - operandTypeCombined = operandTypesCombined[j]; - operandTypeRaw = operandTypesRaw[j]; - operandTypeRaw = getProperty("operandType." + operandTypeRaw + (ui ? ".uiName" : ".name")); - if (opDoc.equals("...")) { - sb.append("..."); - } else { - sb.append(opDoc); - sb.append(":").append(showDataSize ? operandTypeCombined : operandTypeRaw); - } - } - } else { - if (!first) { - sb.append(", "); - } else { - first = false; - } - operandTypeRaw = getProperty("operandType." + operandTypeRaw + (ui ? ".uiName" : ".name")); - - if (opDoc.equals(operandTypeRaw)) { - sb.append(showDataSize ? operandTypeCombined : operandTypeRaw); - } else { - sb.append(opDoc).append(":").append(showDataSize ? operandTypeCombined : operandTypeRaw); - } - } - } - sb.append("
").append(NEWLINE); - } - - sb.append("
").append(insShortDescription).append("
").append(NEWLINE); - - if (!insDescription.trim().isEmpty()) { - sb.append("
").append("").append(getProperty("ui.description")).append("").append(insDescription).append("
").append(NEWLINE); - } - sb.append("
").append(getProperty("ui.stack")).append("").append(stack).append("").append("
").append(NEWLINE); - boolean flagsPrinted = false; - - AVM2InstructionFlag flags[] = def.flags.clone(); - Arrays.sort(flags, Enum::compareTo); - - for (AVM2InstructionFlag fl : flags) { - if (!flagsPrinted) { - flagsPrinted = true; - sb.append("").append(getProperty("ui.flags")).append("").append("
").append(NEWLINE).append("
    ").append(NEWLINE); - } - sb.append("\t
  • ").append(flagDescriptions.get(fl)); - if (fl == AVM2InstructionFlag.DEPRECATED) { - String depDetail = getProperty("instruction." + insName + ".deprecated"); - if (depDetail != null) { - sb.append(": ").append(depDetail).append(""); - } - } - sb.append("
  • ").append(NEWLINE); - } - if (flagsPrinted) { - sb.append("
").append(NEWLINE); - } - sb.append("").append(NEWLINE); - if (standalone) { - sb.append(htmlFooter()); - } - String r = sb.toString(); - docsCache.put(cacheKey, r); - return r; - } - - public static String getStyle() { - String cached = docsCache.get("__style"); - if (cached != null) { - return cached; - } - String style = ""; - InputStream is = As3PCodeDocs.class.getResourceAsStream("/com/jpexs/decompiler/flash/docs/docs.css"); - if (is == null) { - Logger.getLogger(As3PCodeDocs.class.getName()).log(Level.SEVERE, "docs.css needed for documentation not found"); - } else { - style = new String(Helper.readStream(is), Utf8Helper.charset); - } - - docsCache.put("__style", style); - return style; - } - - public static String getJs() { - String cached = docsCache.get("__js"); - if (cached != null) { - return cached; - } - String js = ""; - InputStream is = As3PCodeDocs.class.getResourceAsStream("/com/jpexs/decompiler/flash/docs/docs.js"); - if (is == null) { - Logger.getLogger(As3PCodeDocs.class.getName()).log(Level.SEVERE, "docs.js needed for documentation not found"); - } else { - js = new String(Helper.readStream(is), Utf8Helper.charset); - } - - docsCache.put("__js", js); - return js; - } - - private static String getISO8601StringForDate(Date date) { - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - return dateFormat.format(date); - } - - public static String getAllInstructionDocs() { - - String jsData = ""; - jsData += "var txt_filter_hide = \"" + getProperty("ui.filter.hide") + "\";" + NEWLINE; - jsData += "var txt_filter_byname = \"" + getProperty("ui.filter.byname") + "\";" + NEWLINE; - jsData += "var txt_filter_order = \"" + getProperty("ui.filter.order") + "\";" + NEWLINE; - jsData += "var txt_filter_order_code = \"" + getProperty("ui.filter.order.code") + "\";" + NEWLINE; - jsData += "var txt_filter_order_name = \"" + getProperty("ui.filter.order.name") + "\";" + NEWLINE; - - jsData += "var order_set = \"name\";"; - jsData += "var flags_set = {};" + NEWLINE; - jsData += "var flags = {};" + NEWLINE; - for (AVM2InstructionFlag f : AVM2InstructionFlag.values()) { - jsData += "flags[\"" + makeIdent(f.toString()) + "\"] = \"" + Helper.escapeJavaString(flagDescriptions.get(f)) + "\";" + NEWLINE; - jsData += "flags_set[\"" + makeIdent(f.toString()) + "\"] = false;" + NEWLINE; - } - - AVM2InstructionFlag[] hideFlags = new AVM2InstructionFlag[]{AVM2InstructionFlag.NO_FLASH_PLAYER}; - for (AVM2InstructionFlag f : hideFlags) { - jsData += "flags_set[\"" + makeIdent(f.toString()) + "\"] = true;" + NEWLINE; - } - - StringBuilder sb = new StringBuilder(); - sb.append(htmlHeader(jsData + getJs(), getStyle())); - sb.append("\t\t

").append(getProperty("ui.list.heading")).append("

").append(NEWLINE); - sb.append(""); - sb.append("\t\t
    ").append(NEWLINE); - Set s = new TreeSet<>(nameToDef.keySet()); - for (String name : s) { - InstructionDefinition def = nameToDef.get(name); - if (def == null) { - continue; - } - sb.append("\t\t\t
  • ").append(NEWLINE); - sb.append("\t\t\t\t").append(getDocsForIns(def, true, false, false).trim().replace(NEWLINE, NEWLINE + "\t\t\t\t")).append(NEWLINE); - sb.append("\t\t\t
  • ").append(NEWLINE); - } - sb.append("\t\t
").append(NEWLINE); - sb.append("\t").append(NEWLINE); - sb.append(htmlFooter()); - return sb.toString(); - } - - public static void main(String[] args) throws UnsupportedEncodingException { - System.out.println(getAllInstructionDocs()); - } -} +package com.jpexs.decompiler.flash.docs; + +import com.jpexs.decompiler.flash.ApplicationInfo; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2InstructionFlag; +import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition; +import com.jpexs.helpers.Cache; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Generator for AVM2 instruction set documentation. + * + * @author JPEXS + */ +public class As3PCodeDocs { + + private static ResourceBundle prop; + + private static Map flagDescriptions = new HashMap<>(); + + private static Cache docsCache = Cache.getInstance(false, true, "as3DocsCache"); + + private static Map nameToDef = new HashMap<>(); + + static final String NEWLINE = "\r\n"; + + static { + prop = ResourceBundle.getBundle("com.jpexs.decompiler.flash.locales.docs.pcode.AS3"); + for (InstructionDefinition def : AVM2Code.allInstructionSet) { + if (def == null) { + continue; + } + nameToDef.put(def.instructionName, def); + } + + for (AVM2InstructionFlag flg : AVM2InstructionFlag.values()) { + String flagIdent = makeIdent(flg.toString()); + String flagDescription = getProperty("instructionFlag." + flagIdent); + flagDescriptions.put(flg, flagDescription); + } + } + + private static String makeIdent(String name) { + StringBuilder identName = new StringBuilder(); + boolean cap = false; + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (c == '_') { + cap = true; + continue; + } + if (cap) { + identName.append(c); + cap = false; + } else { + identName.append(Character.toLowerCase(c)); + } + } + return identName.toString(); + } + + public static String getDocsForIns(String insName, boolean showDataSize, boolean ui, boolean withStyle) { + if (!nameToDef.containsKey(insName)) { + return null; + } + return getDocsForIns(nameToDef.get(insName), showDataSize, ui, withStyle); + } + + private static String getProperty(String name) { + if (prop.containsKey(name)) { + return Helper.escapeHTML(prop.getString(name)); + } + return null; + } + + private static String htmlFooter() { + StringBuilder sb = new StringBuilder(); + + sb.append(""); + return sb.toString(); + } + + private static String meta(String name, String content) { + return "\t\t" + NEWLINE; + } + + private static String metaProp(String name, String content) { + return "\t\t" + NEWLINE; + } + + private static String meta(String name, Date content) { + return "\t\t" + NEWLINE; + } + + private static String htmlHeader(String js, String style) { + Date dateGenerated = new Date(); + StringBuilder sb = new StringBuilder(); + sb.append("").append(NEWLINE). + append("").append(NEWLINE). + append("\t").append(NEWLINE); + if (style != null && !style.isEmpty()) { + sb.append("\t\t").append(NEWLINE); + } + if (js != null && !js.isEmpty()) { + sb.append("\t\t").append(NEWLINE); + } + sb.append("\t\t").append(NEWLINE). + append(meta("generator", ApplicationInfo.applicationVerName)). + append(meta("description", getProperty("ui.list.pageDescription"))). + append(metaProp("og:title", getProperty("ui.list.pageTitle"))). + append(metaProp("og:type", "article")). + append(metaProp("og:description", getProperty("ui.list.pageDescription"))). + append(meta("date", dateGenerated)). + append("\t\t").append(getProperty("ui.list.documentTitle")).append("").append(NEWLINE). + append("\t").append(NEWLINE); + return sb.toString(); + } + + public static String getDocsForIns(InstructionDefinition def, boolean showDataSize, boolean ui, boolean standalone) { + final String cacheKey = def.instructionName + "|" + (showDataSize ? 1 : 0) + "|" + (ui ? 1 : 0) + "|" + (standalone ? 1 : 0); + String v = docsCache.get(cacheKey); + if (v != null) { + return v; + } + + StringBuilder sb = new StringBuilder(); + if (standalone) { + sb.append(htmlHeader("", getStyle())); + } + String insName = def.instructionName; + + String insShortDescription = getProperty("instruction." + insName + ".shortDescription"); + String insDescription = getProperty("instruction." + insName + ".description"); + String stackBefore = def.hasFlag(AVM2InstructionFlag.UNKNOWN_STACK) ? "" : getProperty("instruction." + insName + ".stackBefore"); + String stackAfter = def.hasFlag(AVM2InstructionFlag.UNKNOWN_STACK) ? "" : getProperty("instruction." + insName + ".stackAfter"); + if (stackBefore.trim().isEmpty()) { + stackBefore = getProperty("ui.stack.before.empty"); + } else { + stackBefore = getProperty("ui.stack.before") + stackBefore; + } + if (stackAfter.trim().isEmpty()) { + stackAfter = getProperty("ui.stack.before.empty"); + } else { + stackAfter = getProperty("ui.stack.before") + stackAfter; + } + stackBefore = "" + stackBefore + ""; + stackAfter = "" + stackAfter + ""; + + String stack = def.hasFlag(AVM2InstructionFlag.UNKNOWN_STACK) ? getProperty("ui.unknown") : stackBefore + "" + getProperty("ui.stack.to") + "" + stackAfter; + String operandsDoc = def.hasFlag(AVM2InstructionFlag.UNKNOWN_OPERANDS) ? getProperty("ui.unknown") : getProperty("instruction." + insName + ".operands"); + + sb.append("<"); + sb.append(standalone ? "body" : "div"); + sb.append(" class=\"instruction"); + + for (AVM2InstructionFlag fl : def.flags) { + sb.append(" instruction-flag-").append(makeIdent(fl.toString())); + } + sb.append("\">"); + + sb.append("
").append(String.format("0x%02X", def.instructionCode)).append(" ").append(insName).append(""); + + if (def.hasFlag(AVM2InstructionFlag.UNKNOWN_OPERANDS)) { + sb.append(" ").append(getProperty("ui.unknown")).append(NEWLINE); + } else { + String[] operandsDocs = operandsDoc.split(", ?"); + boolean first = true; + if (def.operands.length > 0) { + sb.append(" "); + } + for (int i = 0; i < def.operands.length; i++) { + int op = def.operands[i]; + String opDoc = operandsDocs[i]; + String operandTypeRaw = AVM2Code.operandTypeToString(op, false); + String operandTypeCombined = AVM2Code.operandTypeToString(op, true); + if (operandTypeCombined.contains(", ")) { + String[] operandTypesCombined = operandTypeCombined.split(", ?"); + String[] operandTypesRaw = operandTypeRaw.split(", ?"); + + for (int j = 0; j < operandTypesCombined.length; j++) { + if (!first) { + sb.append(", "); + } else { + first = false; + } + opDoc = operandsDocs[i + j]; + operandTypeCombined = operandTypesCombined[j]; + operandTypeRaw = operandTypesRaw[j]; + operandTypeRaw = getProperty("operandType." + operandTypeRaw + (ui ? ".uiName" : ".name")); + if (opDoc.equals("...")) { + sb.append("..."); + } else { + sb.append(opDoc); + sb.append(":").append(showDataSize ? operandTypeCombined : operandTypeRaw); + } + } + } else { + if (!first) { + sb.append(", "); + } else { + first = false; + } + operandTypeRaw = getProperty("operandType." + operandTypeRaw + (ui ? ".uiName" : ".name")); + + if (opDoc.equals(operandTypeRaw)) { + sb.append(showDataSize ? operandTypeCombined : operandTypeRaw); + } else { + sb.append(opDoc).append(":").append(showDataSize ? operandTypeCombined : operandTypeRaw); + } + } + } + sb.append("
").append(NEWLINE); + } + + sb.append("
").append(insShortDescription).append("
").append(NEWLINE); + + if (!insDescription.trim().isEmpty()) { + sb.append("
").append("").append(getProperty("ui.description")).append("").append(insDescription).append("
").append(NEWLINE); + } + sb.append("
").append(getProperty("ui.stack")).append("").append(stack).append("").append("
").append(NEWLINE); + boolean flagsPrinted = false; + + AVM2InstructionFlag[] flags = def.flags.clone(); + Arrays.sort(flags, Enum::compareTo); + + for (AVM2InstructionFlag fl : flags) { + if (!flagsPrinted) { + flagsPrinted = true; + sb.append("").append(getProperty("ui.flags")).append("").append("
").append(NEWLINE).append("
    ").append(NEWLINE); + } + sb.append("\t
  • ").append(flagDescriptions.get(fl)); + if (fl == AVM2InstructionFlag.DEPRECATED) { + String depDetail = getProperty("instruction." + insName + ".deprecated"); + if (depDetail != null) { + sb.append(": ").append(depDetail).append(""); + } + } + sb.append("
  • ").append(NEWLINE); + } + if (flagsPrinted) { + sb.append("
").append(NEWLINE); + } + sb.append("").append(NEWLINE); + if (standalone) { + sb.append(htmlFooter()); + } + String r = sb.toString(); + docsCache.put(cacheKey, r); + return r; + } + + public static String getStyle() { + String cached = docsCache.get("__style"); + if (cached != null) { + return cached; + } + String style = ""; + InputStream is = As3PCodeDocs.class.getResourceAsStream("/com/jpexs/decompiler/flash/docs/docs.css"); + if (is == null) { + Logger.getLogger(As3PCodeDocs.class.getName()).log(Level.SEVERE, "docs.css needed for documentation not found"); + } else { + style = new String(Helper.readStream(is), Utf8Helper.charset); + } + + docsCache.put("__style", style); + return style; + } + + public static String getJs() { + String cached = docsCache.get("__js"); + if (cached != null) { + return cached; + } + String js = ""; + InputStream is = As3PCodeDocs.class.getResourceAsStream("/com/jpexs/decompiler/flash/docs/docs.js"); + if (is == null) { + Logger.getLogger(As3PCodeDocs.class.getName()).log(Level.SEVERE, "docs.js needed for documentation not found"); + } else { + js = new String(Helper.readStream(is), Utf8Helper.charset); + } + + docsCache.put("__js", js); + return js; + } + + private static String getISO8601StringForDate(Date date) { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + return dateFormat.format(date); + } + + public static String getAllInstructionDocs() { + + String jsData = ""; + jsData += "var txt_filter_hide = \"" + getProperty("ui.filter.hide") + "\";" + NEWLINE; + jsData += "var txt_filter_byname = \"" + getProperty("ui.filter.byname") + "\";" + NEWLINE; + jsData += "var txt_filter_order = \"" + getProperty("ui.filter.order") + "\";" + NEWLINE; + jsData += "var txt_filter_order_code = \"" + getProperty("ui.filter.order.code") + "\";" + NEWLINE; + jsData += "var txt_filter_order_name = \"" + getProperty("ui.filter.order.name") + "\";" + NEWLINE; + + jsData += "var order_set = \"name\";"; + jsData += "var flags_set = {};" + NEWLINE; + jsData += "var flags = {};" + NEWLINE; + for (AVM2InstructionFlag f : AVM2InstructionFlag.values()) { + jsData += "flags[\"" + makeIdent(f.toString()) + "\"] = \"" + Helper.escapeJavaString(flagDescriptions.get(f)) + "\";" + NEWLINE; + jsData += "flags_set[\"" + makeIdent(f.toString()) + "\"] = false;" + NEWLINE; + } + + AVM2InstructionFlag[] hideFlags = new AVM2InstructionFlag[]{AVM2InstructionFlag.NO_FLASH_PLAYER}; + for (AVM2InstructionFlag f : hideFlags) { + jsData += "flags_set[\"" + makeIdent(f.toString()) + "\"] = true;" + NEWLINE; + } + + StringBuilder sb = new StringBuilder(); + sb.append(htmlHeader(jsData + getJs(), getStyle())); + sb.append("\t\t

").append(getProperty("ui.list.heading")).append("

").append(NEWLINE); + sb.append(""); + sb.append("\t\t
    ").append(NEWLINE); + Set s = new TreeSet<>(nameToDef.keySet()); + for (String name : s) { + InstructionDefinition def = nameToDef.get(name); + if (def == null) { + continue; + } + sb.append("\t\t\t
  • ").append(NEWLINE); + sb.append("\t\t\t\t").append(getDocsForIns(def, true, false, false).trim().replace(NEWLINE, NEWLINE + "\t\t\t\t")).append(NEWLINE); + sb.append("\t\t\t
  • ").append(NEWLINE); + } + sb.append("\t\t
").append(NEWLINE); + sb.append("\t").append(NEWLINE); + sb.append(htmlFooter()); + return sb.toString(); + } + + public static void main(String[] args) throws UnsupportedEncodingException { + System.out.println(getAllInstructionDocs()); + } +} 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/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 c368ac4cb..7be668608 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 @@ -1,207 +1,207 @@ -/* - * 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.helpers; - -import com.jpexs.decompiler.flash.tags.enums.ImageFormat; -import com.jpexs.decompiler.flash.types.RGBA; -import com.jpexs.helpers.Helper; -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.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Iterator; -import java.util.Locale; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; -import org.monte.media.jpeg.CMYKJPEGImageReader; -import org.monte.media.jpeg.CMYKJPEGImageReaderSpi; - -/** - * - * @author JPEXS - */ -public class ImageHelper { - - static { - ImageIO.setUseCache(false); - } - - public static BufferedImage read(byte[] data) throws IOException { - return read(new ByteArrayInputStream(data)); - } - - public static BufferedImage read(InputStream input) throws IOException { - BufferedImage in; - byte data[] = Helper.readStream(input); - try (ImageInputStream iis = ImageIO.createImageInputStream(new ByteArrayInputStream(data))) { - CMYKJPEGImageReader r = new CMYKJPEGImageReader(new CMYKJPEGImageReaderSpi()); - r.setInput(iis); - in = r.read(0); - } catch (IOException | ArrayIndexOutOfBoundsException ex) { - try { - in = ImageIO.read(ImageIO.createImageInputStream(new ByteArrayInputStream(data))); - } catch (IOException ex1) { - return null; - } - } - - int type = in.getType(); - if (type != BufferedImage.TYPE_INT_ARGB_PRE && type != BufferedImage.TYPE_INT_RGB) { - // convert to ARGB - int width = in.getWidth(); - int height = in.getHeight(); - int[] imgData = in.getRGB(0, 0, width, height, null, 0, width); - BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); - newImage.getRaster().setDataElements(0, 0, width, height, imgData); - return newImage; - } - - return in; - } - - public static void write(BufferedImage image, ImageFormat format, File output) throws IOException { - String formatName = getImageFormatString(format).toUpperCase(Locale.ENGLISH); - if (format == ImageFormat.JPEG) { - image = fixImageIOJpegBug(image); - } - - ImageIO.write(image, formatName, output); - } - - public static void write(BufferedImage image, ImageFormat format, OutputStream output) throws IOException { - String formatName = getImageFormatString(format).toUpperCase(Locale.ENGLISH); - if (format == ImageFormat.JPEG) { - image = fixImageIOJpegBug(image); - } - - ImageIO.write(image, formatName, output); - } - - public static void write(BufferedImage image, ImageFormat format, ByteArrayOutputStream output) { - String formatName = getImageFormatString(format).toUpperCase(Locale.ENGLISH); - if (format == ImageFormat.JPEG) { - image = fixImageIOJpegBug(image); - } else { - if (image.getType() == BufferedImage.TYPE_INT_ARGB_PRE) { - BufferedImage image2 = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); - int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); - divideAlpha(pixels); - - int[] pixels2 = ((DataBufferInt) image2.getRaster().getDataBuffer()).getData(); - for (int i = 0; i < pixels.length; i++) { - pixels2[i] = pixels[i]; - } - - image = image2; - } - } - - try { - ImageIO.write(image, formatName, output); - } catch (IOException ex) { - Logger.getLogger(ImageHelper.class.getName()).log(Level.SEVERE, null, ex); - } - } - - private static int max255(float val) { - if (val > 255) { - return 255; - } - return (int) val; - } - - private static void divideAlpha(int[] pixels) { - for (int i = 0; i < pixels.length; i++) { - pixels[i] = divideAlpha(pixels[i]); - } - } - - private static int divideAlpha(int value) { - int a = (value >> 24) & 0xFF; - int r = (value >> 16) & 0xFF; - int g = (value >> 8) & 0xFF; - int b = value & 0xFF; - float multiplier = a == 0 ? 0 : 255.0f / a; - r = max255(r * multiplier); - g = max255(g * multiplier); - b = max255(b * multiplier); - return RGBA.toInt(r, g, b, a); - } - - private static BufferedImage fixImageIOJpegBug(BufferedImage image) { - int type = image.getType(); - if (type != BufferedImage.TYPE_INT_RGB) { - // convert to RGB without alpha channel - int width = image.getWidth(); - int height = image.getHeight(); - BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - int[] pixels2 = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); - int[] pixels = image.getRGB(0, 0, width, height, null, 0, width); - for (int i = 0; i < pixels.length; i++) { - pixels2[i] = pixels[i] & 0xffffff; - } - - return newImage; - } - - return image; - } - - public static String getImageFormatString(ImageFormat format) { - switch (format) { - case UNKNOWN: - return "unk"; - case JPEG: - return "jpg"; - case GIF: - return "gif"; - case PNG: - return "png"; - case BMP: - return "bmp"; - } - - throw new Error("Unsuported image format: " + format); - } - - public static Dimension getDimesion(InputStream input) throws IOException { - try (ImageInputStream in = ImageIO.createImageInputStream(input)) { - final Iterator readers = ImageIO.getImageReaders(in); - if (readers.hasNext()) { - ImageReader reader = readers.next(); - try { - reader.setInput(in); - return new Dimension(reader.getWidth(0), reader.getHeight(0)); - } finally { - reader.dispose(); - } - } - } catch (IOException ex) { - } - - BufferedImage image = read(input); - return new Dimension(image.getWidth(), image.getHeight()); - } -} +/* + * 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.helpers; + +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.helpers.Helper; +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.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; +import org.monte.media.jpeg.CMYKJPEGImageReader; +import org.monte.media.jpeg.CMYKJPEGImageReaderSpi; + +/** + * + * @author JPEXS + */ +public class ImageHelper { + + static { + ImageIO.setUseCache(false); + } + + public static BufferedImage read(byte[] data) throws IOException { + return read(new ByteArrayInputStream(data)); + } + + public static BufferedImage read(InputStream input) throws IOException { + BufferedImage in; + byte[] data = Helper.readStream(input); + try (ImageInputStream iis = ImageIO.createImageInputStream(new ByteArrayInputStream(data))) { + CMYKJPEGImageReader r = new CMYKJPEGImageReader(new CMYKJPEGImageReaderSpi()); + r.setInput(iis); + in = r.read(0); + } catch (IOException | ArrayIndexOutOfBoundsException ex) { + try { + in = ImageIO.read(ImageIO.createImageInputStream(new ByteArrayInputStream(data))); + } catch (IOException ex1) { + return null; + } + } + + int type = in.getType(); + if (type != BufferedImage.TYPE_INT_ARGB_PRE && type != BufferedImage.TYPE_INT_RGB) { + // convert to ARGB + int width = in.getWidth(); + int height = in.getHeight(); + int[] imgData = in.getRGB(0, 0, width, height, null, 0, width); + BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); + newImage.getRaster().setDataElements(0, 0, width, height, imgData); + return newImage; + } + + return in; + } + + public static void write(BufferedImage image, ImageFormat format, File output) throws IOException { + String formatName = getImageFormatString(format).toUpperCase(Locale.ENGLISH); + if (format == ImageFormat.JPEG) { + image = fixImageIOJpegBug(image); + } + + ImageIO.write(image, formatName, output); + } + + public static void write(BufferedImage image, ImageFormat format, OutputStream output) throws IOException { + String formatName = getImageFormatString(format).toUpperCase(Locale.ENGLISH); + if (format == ImageFormat.JPEG) { + image = fixImageIOJpegBug(image); + } + + ImageIO.write(image, formatName, output); + } + + public static void write(BufferedImage image, ImageFormat format, ByteArrayOutputStream output) { + String formatName = getImageFormatString(format).toUpperCase(Locale.ENGLISH); + if (format == ImageFormat.JPEG) { + image = fixImageIOJpegBug(image); + } else { + if (image.getType() == BufferedImage.TYPE_INT_ARGB_PRE) { + BufferedImage image2 = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + divideAlpha(pixels); + + int[] pixels2 = ((DataBufferInt) image2.getRaster().getDataBuffer()).getData(); + for (int i = 0; i < pixels.length; i++) { + pixels2[i] = pixels[i]; + } + + image = image2; + } + } + + try { + ImageIO.write(image, formatName, output); + } catch (IOException ex) { + Logger.getLogger(ImageHelper.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private static int max255(float val) { + if (val > 255) { + return 255; + } + return (int) val; + } + + private static void divideAlpha(int[] pixels) { + for (int i = 0; i < pixels.length; i++) { + pixels[i] = divideAlpha(pixels[i]); + } + } + + private static int divideAlpha(int value) { + int a = (value >> 24) & 0xFF; + int r = (value >> 16) & 0xFF; + int g = (value >> 8) & 0xFF; + int b = value & 0xFF; + float multiplier = a == 0 ? 0 : 255.0f / a; + r = max255(r * multiplier); + g = max255(g * multiplier); + b = max255(b * multiplier); + return RGBA.toInt(r, g, b, a); + } + + private static BufferedImage fixImageIOJpegBug(BufferedImage image) { + int type = image.getType(); + if (type != BufferedImage.TYPE_INT_RGB) { + // convert to RGB without alpha channel + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + int[] pixels2 = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + int[] pixels = image.getRGB(0, 0, width, height, null, 0, width); + for (int i = 0; i < pixels.length; i++) { + pixels2[i] = pixels[i] & 0xffffff; + } + + return newImage; + } + + return image; + } + + public static String getImageFormatString(ImageFormat format) { + switch (format) { + case UNKNOWN: + return "unk"; + case JPEG: + return "jpg"; + case GIF: + return "gif"; + case PNG: + return "png"; + case BMP: + return "bmp"; + } + + throw new Error("Unsuported image format: " + format); + } + + public static Dimension getDimesion(InputStream input) throws IOException { + try (ImageInputStream in = ImageIO.createImageInputStream(input)) { + final Iterator readers = ImageIO.getImageReaders(in); + if (readers.hasNext()) { + ImageReader reader = readers.next(); + try { + reader.setInput(in); + return new Dimension(reader.getWidth(0), reader.getHeight(0)); + } finally { + reader.dispose(); + } + } + } catch (IOException ex) { + } + + BufferedImage image = read(input); + return new Dimension(image.getWidth(), image.getHeight()); + } +} 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/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 4a68db226..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; @@ -72,9 +73,9 @@ public class DoABCTag extends Tag implements ABCContainerTag { @Override public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { 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/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..f20ad0899 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 @@ -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(); 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/src/com/jpexs/browsers/cache/CacheEntry.java b/src/com/jpexs/browsers/cache/CacheEntry.java index 9da50045d..d8fd12c75 100644 --- a/src/com/jpexs/browsers/cache/CacheEntry.java +++ b/src/com/jpexs/browsers/cache/CacheEntry.java @@ -1,111 +1,111 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.browsers.cache; - -import com.jpexs.helpers.LimitedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; -import java.util.zip.GZIPInputStream; -import java.util.zip.InflaterInputStream; - -/** - * - * @author JPEXS - */ -public abstract class CacheEntry { - - public abstract String getRequestURL(); - - public abstract Map getResponseHeaders(); - - public abstract String getStatusLine(); - - public abstract String getRequestMethod(); - - public abstract InputStream getResponseRawDataStream(); - - public InputStream getResponseDataStream() { - String contentLengthStr = getHeader("Content-Length"); - int contentLength = -1; - if (contentLengthStr != null) { - try { - contentLength = Integer.parseInt(contentLengthStr); - } catch (NumberFormatException nex) { - } - } - final InputStream rawIs = getResponseRawDataStream(); - InputStream is = rawIs; - if (contentLength > -1) { - is = new LimitedInputStream(is, contentLength); - } - - String encoding = getHeader("Content-Encoding"); - if (encoding != null) { - switch (encoding) { - case "gzip": - try { - is = new GZIPInputStream(is); - } catch (IOException ex) { - is = null; - //ignore - } - break; - case "deflate": - is = new InflaterInputStream(is); - break; - default: //unknown - return null; - } - } - if ("chunked".equals(getHeader("Transfer-Encoding"))) { - is = new ChunkedInputStream(is); - } - return is; - } - - @Override - public String toString() { - return getRequestURL(); - } - - public int getStatusCode() { - String st = getStatusLine(); - if (st == null) { - return 0; - } - String parts[] = st.split(" "); - try { - return Integer.parseInt(parts[1]); - } catch (NumberFormatException nfe) { - return 0; - } - } - - public String getHeader(String header) { - Map m = getResponseHeaders(); - if (m == null) { - return null; - } - for (String k : m.keySet()) { - if (k.toLowerCase().equals(header.toLowerCase())) { - return m.get(k); - } - } - return null; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.browsers.cache; + +import com.jpexs.helpers.LimitedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +/** + * + * @author JPEXS + */ +public abstract class CacheEntry { + + public abstract String getRequestURL(); + + public abstract Map getResponseHeaders(); + + public abstract String getStatusLine(); + + public abstract String getRequestMethod(); + + public abstract InputStream getResponseRawDataStream(); + + public InputStream getResponseDataStream() { + String contentLengthStr = getHeader("Content-Length"); + int contentLength = -1; + if (contentLengthStr != null) { + try { + contentLength = Integer.parseInt(contentLengthStr); + } catch (NumberFormatException nex) { + } + } + final InputStream rawIs = getResponseRawDataStream(); + InputStream is = rawIs; + if (contentLength > -1) { + is = new LimitedInputStream(is, contentLength); + } + + String encoding = getHeader("Content-Encoding"); + if (encoding != null) { + switch (encoding) { + case "gzip": + try { + is = new GZIPInputStream(is); + } catch (IOException ex) { + is = null; + //ignore + } + break; + case "deflate": + is = new InflaterInputStream(is); + break; + default: //unknown + return null; + } + } + if ("chunked".equals(getHeader("Transfer-Encoding"))) { + is = new ChunkedInputStream(is); + } + return is; + } + + @Override + public String toString() { + return getRequestURL(); + } + + public int getStatusCode() { + String st = getStatusLine(); + if (st == null) { + return 0; + } + String[] parts = st.split(" "); + try { + return Integer.parseInt(parts[1]); + } catch (NumberFormatException nfe) { + return 0; + } + } + + public String getHeader(String header) { + Map m = getResponseHeaders(); + if (m == null) { + return null; + } + for (String k : m.keySet()) { + if (k.toLowerCase().equals(header.toLowerCase())) { + return m.get(k); + } + } + return null; + } +} diff --git a/src/com/jpexs/browsers/cache/chrome/BlockFileHeader.java b/src/com/jpexs/browsers/cache/chrome/BlockFileHeader.java index a5734a150..744fa64e3 100644 --- a/src/com/jpexs/browsers/cache/chrome/BlockFileHeader.java +++ b/src/com/jpexs/browsers/cache/chrome/BlockFileHeader.java @@ -1,83 +1,83 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.browsers.cache.chrome; - -import java.io.IOException; -import java.io.InputStream; - -/** - * - * @author JPEXS - */ -public class BlockFileHeader { - - protected static final int kBlockHeaderSize = 8192; // Two pages: almost 64k entries - - private static final int kMaxBlocks = (kBlockHeaderSize - 80) * 8; - - private final long magic; // c3 ca 04 c1 - - private final long version; // 00 00 02 00 - - private final int this_file; - - private final int next_file; - - private final int entry_size; - - private final int num_entries; - - private final int max_entries; - - private int empty[] = new int[4]; - - private int hints[] = new int[4]; - - private final int updating; - - private int user[] = new int[5]; - - private final long allocation_map[]; - - public BlockFileHeader(InputStream is) throws IOException { - this.allocation_map = new long[kMaxBlocks / 32]; - IndexInputStream iis = new IndexInputStream(is); - magic = iis.readUInt32(); - version = iis.readUInt32(); - this_file = iis.readInt16(); - next_file = iis.readInt16(); - entry_size = iis.readInt32(); - num_entries = iis.readInt32(); - max_entries = iis.readInt32(); - empty = new int[4]; - for (int i = 0; i < 4; i++) { - empty[i] = iis.readInt32(); - } - hints = new int[4]; - for (int i = 0; i < 4; i++) { - hints[i] = iis.readInt32(); - } - updating = iis.readInt32(); - user = new int[5]; - for (int i = 0; i < 5; i++) { - user[i] = iis.readInt32(); - } - for (int i = 0; i < allocation_map.length; i++) { - allocation_map[i] = iis.readUInt32(); - } - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.browsers.cache.chrome; + +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @author JPEXS + */ +public class BlockFileHeader { + + protected static final int kBlockHeaderSize = 8192; // Two pages: almost 64k entries + + private static final int kMaxBlocks = (kBlockHeaderSize - 80) * 8; + + private final long magic; // c3 ca 04 c1 + + private final long version; // 00 00 02 00 + + private final int this_file; + + private final int next_file; + + private final int entry_size; + + private final int num_entries; + + private final int max_entries; + + private int[] empty = new int[4]; + + private int[] hints = new int[4]; + + private final int updating; + + private int[] user = new int[5]; + + private final long allocation_map[]; + + public BlockFileHeader(InputStream is) throws IOException { + this.allocation_map = new long[kMaxBlocks / 32]; + IndexInputStream iis = new IndexInputStream(is); + magic = iis.readUInt32(); + version = iis.readUInt32(); + this_file = iis.readInt16(); + next_file = iis.readInt16(); + entry_size = iis.readInt32(); + num_entries = iis.readInt32(); + max_entries = iis.readInt32(); + empty = new int[4]; + for (int i = 0; i < 4; i++) { + empty[i] = iis.readInt32(); + } + hints = new int[4]; + for (int i = 0; i < 4; i++) { + hints[i] = iis.readInt32(); + } + updating = iis.readInt32(); + user = new int[5]; + for (int i = 0; i < 5; i++) { + user[i] = iis.readInt32(); + } + for (int i = 0; i < allocation_map.length; i++) { + allocation_map[i] = iis.readUInt32(); + } + } +} diff --git a/src/com/jpexs/browsers/cache/chrome/CacheAddr.java b/src/com/jpexs/browsers/cache/chrome/CacheAddr.java index 6d09613be..0291b2a3a 100644 --- a/src/com/jpexs/browsers/cache/chrome/CacheAddr.java +++ b/src/com/jpexs/browsers/cache/chrome/CacheAddr.java @@ -1,151 +1,151 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.browsers.cache.chrome; - -import com.jpexs.browsers.cache.RafInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; -import java.util.Map; - -/** - * - * @author JPEXS - */ -public class CacheAddr { - - private static final int EXTERNAL = 0; - - private static final int RANKINGS = 1; - - private static final int BLOCK_256 = 2; - - private static final int BLOCK_1K = 3; - - private static final int BLOCK_4K = 4; - - private static final int BLOCK_FILES = 5; - - private static final int BLOCK_ENTRIES = 6; - - 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 int blockSizes[] = new int[]{0, 36, 256, 1024, 4096, 8, 104, 48}; - - private static final long kInitializedMask = 0x80000000L; - - private static final long kFileTypeMask = 0x70000000L; - - private static final int kFileTypeOffset = 28; - - private static final long kReservedBitsMask = 0x0c000000L; - - private static final long kNumBlocksMask = 0x03000000L; - - private static final int kNumBlocksOffset = 24; - - private static final long kFileSelectorMask = 0x00ff0000L; - - private static final int FileSelectorOffset = 16; - - private static final long kStartBlockMask = 0x0000FFFFL; - - private static final long kFileNameMask = 0x0FFFFFFFL; - - private final boolean initialized; - - private final int fileType; - - private int numBlocks; - - private int fileSelector; - - private int startBlock; - - private int fileName; - - private final long val; - - private final File rootPath; - - private final Map dataFiles; - - private final File externalFilesDir; - - public CacheAddr(InputStream is, File rootPath, Map dataFiles, File externalFilesDir) throws IOException { - this.dataFiles = dataFiles; - this.rootPath = rootPath; - this.externalFilesDir = externalFilesDir; - IndexInputStream iis = new IndexInputStream(is); - val = iis.readUInt32(); - initialized = (val & kInitializedMask) == kInitializedMask; - fileType = (int) ((val & kFileTypeMask) >> kFileTypeOffset); - if (fileType == EXTERNAL) { - fileName = (int) (val & kFileNameMask); - } else { - numBlocks = (int) ((val & kNumBlocksMask) >> kNumBlocksOffset); - fileSelector = (int) ((val & kFileSelectorMask) >> FileSelectorOffset); - startBlock = (int) (val & kStartBlockMask); - } - } - - @Override - public String toString() { - - String ft = blockNames[fileType]; - if (fileType == EXTERNAL) { - return ft + ":" + fileName; - } - if (!initialized) { - return "uninitialized"; - } - return ft + ": numBlocks " + numBlocks + " fileSelector " + fileSelector + " startBlock " + startBlock; - } - - public InputStream getInputStream() throws IOException { - if (!initialized) { - return null; - } - switch (fileType) { - case EXTERNAL: - String fileNameStr = Long.toHexString(fileName); - while (fileNameStr.length() < 6) { - fileNameStr = "0" + fileNameStr; - } - fileNameStr = "f_" + fileNameStr; - return new RafInputStream(new RandomAccessFile(new File(externalFilesDir, fileNameStr), "r")); - case BLOCK_1K: - case BLOCK_256: - case BLOCK_4K: - - RandomAccessFile raf; - - if (dataFiles.containsKey(fileSelector)) { - raf = dataFiles.get(fileSelector); - } else { - raf = new RandomAccessFile(rootPath + "\\data_" + fileSelector, "r"); - dataFiles.put(fileSelector, raf); - } - raf.seek(BlockFileHeader.kBlockHeaderSize + startBlock * blockSizes[fileType]); - return new RafInputStream(raf); - } - return null; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.browsers.cache.chrome; + +import com.jpexs.browsers.cache.RafInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.util.Map; + +/** + * + * @author JPEXS + */ +public class CacheAddr { + + private static final int EXTERNAL = 0; + + private static final int RANKINGS = 1; + + private static final int BLOCK_256 = 2; + + private static final int BLOCK_1K = 3; + + private static final int BLOCK_4K = 4; + + private static final int BLOCK_FILES = 5; + + private static final int BLOCK_ENTRIES = 6; + + 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 int[] blockSizes = new int[]{0, 36, 256, 1024, 4096, 8, 104, 48}; + + private static final long kInitializedMask = 0x80000000L; + + private static final long kFileTypeMask = 0x70000000L; + + private static final int kFileTypeOffset = 28; + + private static final long kReservedBitsMask = 0x0c000000L; + + private static final long kNumBlocksMask = 0x03000000L; + + private static final int kNumBlocksOffset = 24; + + private static final long kFileSelectorMask = 0x00ff0000L; + + private static final int FileSelectorOffset = 16; + + private static final long kStartBlockMask = 0x0000FFFFL; + + private static final long kFileNameMask = 0x0FFFFFFFL; + + private final boolean initialized; + + private final int fileType; + + private int numBlocks; + + private int fileSelector; + + private int startBlock; + + private int fileName; + + private final long val; + + private final File rootPath; + + private final Map dataFiles; + + private final File externalFilesDir; + + public CacheAddr(InputStream is, File rootPath, Map dataFiles, File externalFilesDir) throws IOException { + this.dataFiles = dataFiles; + this.rootPath = rootPath; + this.externalFilesDir = externalFilesDir; + IndexInputStream iis = new IndexInputStream(is); + val = iis.readUInt32(); + initialized = (val & kInitializedMask) == kInitializedMask; + fileType = (int) ((val & kFileTypeMask) >> kFileTypeOffset); + if (fileType == EXTERNAL) { + fileName = (int) (val & kFileNameMask); + } else { + numBlocks = (int) ((val & kNumBlocksMask) >> kNumBlocksOffset); + fileSelector = (int) ((val & kFileSelectorMask) >> FileSelectorOffset); + startBlock = (int) (val & kStartBlockMask); + } + } + + @Override + public String toString() { + + String ft = blockNames[fileType]; + if (fileType == EXTERNAL) { + return ft + ":" + fileName; + } + if (!initialized) { + return "uninitialized"; + } + return ft + ": numBlocks " + numBlocks + " fileSelector " + fileSelector + " startBlock " + startBlock; + } + + public InputStream getInputStream() throws IOException { + if (!initialized) { + return null; + } + switch (fileType) { + case EXTERNAL: + String fileNameStr = Long.toHexString(fileName); + while (fileNameStr.length() < 6) { + fileNameStr = "0" + fileNameStr; + } + fileNameStr = "f_" + fileNameStr; + return new RafInputStream(new RandomAccessFile(new File(externalFilesDir, fileNameStr), "r")); + case BLOCK_1K: + case BLOCK_256: + case BLOCK_4K: + + RandomAccessFile raf; + + if (dataFiles.containsKey(fileSelector)) { + raf = dataFiles.get(fileSelector); + } else { + raf = new RandomAccessFile(rootPath + "\\data_" + fileSelector, "r"); + dataFiles.put(fileSelector, raf); + } + raf.seek(BlockFileHeader.kBlockHeaderSize + startBlock * blockSizes[fileType]); + return new RafInputStream(raf); + } + return null; + } +} diff --git a/src/com/jpexs/browsers/cache/chrome/EntryStore.java b/src/com/jpexs/browsers/cache/chrome/EntryStore.java index d3114c596..a62073b07 100644 --- a/src/com/jpexs/browsers/cache/chrome/EntryStore.java +++ b/src/com/jpexs/browsers/cache/chrome/EntryStore.java @@ -1,174 +1,174 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.browsers.cache.chrome; - -import com.jpexs.browsers.cache.CacheEntry; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -public class EntryStore extends CacheEntry { - - public static final int ENTRY_NORMAL = 0; - - public static final int ENTRY_EVICTED = 1; // The entry was recently evicted from the cache. - - public static final int ENTRY_DOOMED = 2; // The entry was doomed - - public static final int PARENT_ENTRY = 1; // This entry has children (sparse) entries. - - public static final int CHILD_ENTRY = 1 << 1; - - public long hash; // Full hash of the key. - - public CacheAddr next; // Next entry with the same hash or bucket. - - public CacheAddr rankings_node; // Rankings node for this entry. - - public int reuse_count; // How often is this entry used. - - public int refetch_count; // How often is this fetched from the net. - - public int state; // Current state. - - public long creation_time; - - public int key_len; - - 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 CacheAddr data_addr[] = new CacheAddr[4]; // entry. - - public long flags; // Any combination of EntryFlags. - - 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 EntryStore(InputStream is, File rootDir, Map dataFiles, File externalFilesDir) throws IOException { - IndexInputStream iis = new IndexInputStream(is); - hash = iis.readUInt32(); - next = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); - rankings_node = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); - reuse_count = iis.readInt32(); - refetch_count = iis.readInt32(); - state = iis.readInt32(); - creation_time = iis.readUInt64(); - key_len = iis.readInt32(); - long_key = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); - data_size = new int[4]; - for (int i = 0; i < 4; i++) { - data_size[i] = iis.readInt32(); - } - data_addr = new CacheAddr[4]; - for (int i = 0; i < 4; i++) { - data_addr[i] = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); - } - flags = iis.readUInt32(); - pad = new int[4]; - for (int i = 0; i < 4; i++) { - pad[i] = iis.readInt32(); - } - self_hash = iis.readUInt32(); - key = new byte[256 - 24 * 4]; - if (iis.read(key) != key.length) { - throw new IOException(); - } - } - - public HttpResponseInfo getResponseInfo() { - try { - InputStream is = data_addr[0].getInputStream(); - if (is == null) { - return null; - } - return new HttpResponseInfo(is); - } catch (IOException ex) { - Logger.getLogger(EntryStore.class.getName()).log(Level.SEVERE, null, ex); - return null; - } - } - - public int getResponseDataSize() { - return data_size[1]; - } - - @Override - public InputStream getResponseRawDataStream() { - try { - return data_addr[1].getInputStream(); - } catch (IOException ex) { - Logger.getLogger(EntryStore.class.getName()).log(Level.SEVERE, null, ex); - } - return null; - } - - public String getKey() { - if (key_len < 0) { - return null; - } - return new String(key, 0, key_len > key.length ? key.length : key_len); - } - - @Override - public String getRequestURL() { - return getKey(); - } - - @Override - public Map getResponseHeaders() { - HttpResponseInfo ri = getResponseInfo(); - if (ri == null) { - return new HashMap<>(); - } - List headers = ri.headers; - Map ret = new HashMap<>(); - for (int h = 1; h < headers.size(); h++) { - String hs = headers.get(h); - if (hs.contains(":")) { - String hp[] = hs.split(":"); - ret.put(hp[0].trim(), hp[1].trim()); - } - } - return ret; - } - - @Override - public String getStatusLine() { - HttpResponseInfo ri = getResponseInfo(); - return ri.headers.get(0); - } - - @Override - public String getRequestMethod() { - return "GET"; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.browsers.cache.chrome; + +import com.jpexs.browsers.cache.CacheEntry; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class EntryStore extends CacheEntry { + + public static final int ENTRY_NORMAL = 0; + + public static final int ENTRY_EVICTED = 1; // The entry was recently evicted from the cache. + + public static final int ENTRY_DOOMED = 2; // The entry was doomed + + public static final int PARENT_ENTRY = 1; // This entry has children (sparse) entries. + + public static final int CHILD_ENTRY = 1 << 1; + + public long hash; // Full hash of the key. + + public CacheAddr next; // Next entry with the same hash or bucket. + + public CacheAddr rankings_node; // Rankings node for this entry. + + public int reuse_count; // How often is this entry used. + + public int refetch_count; // How often is this fetched from the net. + + public int state; // Current state. + + public long creation_time; + + public int key_len; + + 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 CacheAddr[] data_addr = new CacheAddr[4]; // entry. + + public long flags; // Any combination of EntryFlags. + + 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 EntryStore(InputStream is, File rootDir, Map dataFiles, File externalFilesDir) throws IOException { + IndexInputStream iis = new IndexInputStream(is); + hash = iis.readUInt32(); + next = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); + rankings_node = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); + reuse_count = iis.readInt32(); + refetch_count = iis.readInt32(); + state = iis.readInt32(); + creation_time = iis.readUInt64(); + key_len = iis.readInt32(); + long_key = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); + data_size = new int[4]; + for (int i = 0; i < 4; i++) { + data_size[i] = iis.readInt32(); + } + data_addr = new CacheAddr[4]; + for (int i = 0; i < 4; i++) { + data_addr[i] = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); + } + flags = iis.readUInt32(); + pad = new int[4]; + for (int i = 0; i < 4; i++) { + pad[i] = iis.readInt32(); + } + self_hash = iis.readUInt32(); + key = new byte[256 - 24 * 4]; + if (iis.read(key) != key.length) { + throw new IOException(); + } + } + + public HttpResponseInfo getResponseInfo() { + try { + InputStream is = data_addr[0].getInputStream(); + if (is == null) { + return null; + } + return new HttpResponseInfo(is); + } catch (IOException ex) { + Logger.getLogger(EntryStore.class.getName()).log(Level.SEVERE, null, ex); + return null; + } + } + + public int getResponseDataSize() { + return data_size[1]; + } + + @Override + public InputStream getResponseRawDataStream() { + try { + return data_addr[1].getInputStream(); + } catch (IOException ex) { + Logger.getLogger(EntryStore.class.getName()).log(Level.SEVERE, null, ex); + } + return null; + } + + public String getKey() { + if (key_len < 0) { + return null; + } + return new String(key, 0, key_len > key.length ? key.length : key_len); + } + + @Override + public String getRequestURL() { + return getKey(); + } + + @Override + public Map getResponseHeaders() { + HttpResponseInfo ri = getResponseInfo(); + if (ri == null) { + return new HashMap<>(); + } + List headers = ri.headers; + Map ret = new HashMap<>(); + for (int h = 1; h < headers.size(); h++) { + String hs = headers.get(h); + if (hs.contains(":")) { + String[] hp = hs.split(":"); + ret.put(hp[0].trim(), hp[1].trim()); + } + } + return ret; + } + + @Override + public String getStatusLine() { + HttpResponseInfo ri = getResponseInfo(); + return ri.headers.get(0); + } + + @Override + public String getRequestMethod() { + return "GET"; + } +} diff --git a/src/com/jpexs/browsers/cache/chrome/HttpResponseInfo.java b/src/com/jpexs/browsers/cache/chrome/HttpResponseInfo.java index 74e3c79c4..c3970cbc0 100644 --- a/src/com/jpexs/browsers/cache/chrome/HttpResponseInfo.java +++ b/src/com/jpexs/browsers/cache/chrome/HttpResponseInfo.java @@ -1,127 +1,127 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.browsers.cache.chrome; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author JPEXS - */ -public class HttpResponseInfo { - - public long flags; - - public int version; - - public long request_time; - - public long response_time; - - public long payload_size; - - public List headers; - - // The version of the response info used when persisting response info. - public static final int RESPONSE_INFO_VERSION = 3; - - // The minimum version supported for deserializing response info. - public static final int RESPONSE_INFO_MINIMUM_VERSION = 1; - - // We reserve up to 8 bits for the version number. - public static final int RESPONSE_INFO_VERSION_MASK = 0xFF; - - // This bit is set if the response info has a cert at the end. - // Version 1 serialized only the end-entity certificate, while subsequent - // versions include the available certificate chain. - public static final int RESPONSE_INFO_HAS_CERT = 1 << 8; - - // This bit is set if the response info has a security-bits field (security - // strength, in bits, of the SSL connection) at the end. - public static final int RESPONSE_INFO_HAS_SECURITY_BITS = 1 << 9; - - // This bit is set if the response info has a cert status at the end. - public static final int RESPONSE_INFO_HAS_CERT_STATUS = 1 << 10; - - // This bit is set if the response info has vary header data. - public static final int RESPONSE_INFO_HAS_VARY_DATA = 1 << 11; - - // This bit is set if the request was cancelled before completion. - public static final int RESPONSE_INFO_TRUNCATED = 1 << 12; - - // This bit is set if the response was received via SPDY. - public static final int RESPONSE_INFO_WAS_SPDY = 1 << 13; - - // This bit is set if the request has NPN negotiated. - public static final int RESPONSE_INFO_WAS_NPN = 1 << 14; - - // This bit is set if the request was fetched via an explicit proxy. - public static final int RESPONSE_INFO_WAS_PROXY = 1 << 15; - - // This bit is set if the response info has an SSL connection status field. - // This contains the ciphersuite used to fetch the resource as well as the - // protocol version, compression method and whether SSLv3 fallback was used. - public static final int RESPONSE_INFO_HAS_SSL_CONNECTION_STATUS = 1 << 16; - - // This bit is set if the response info has protocol version. - public static final int RESPONSE_INFO_HAS_NPN_NEGOTIATED_PROTOCOL = 1 << 17; - - // This bit is set if the response info has connection info. - public static final int RESPONSE_INFO_HAS_CONNECTION_INFO = 1 << 18; - - // This bit is set if the request has http authentication. - public static final int RESPONSE_INFO_USE_HTTP_AUTHENTICATION = 1 << 19; - - public String getHeaderValue(String header) { - for (String h : headers) { - if (h.contains(":")) { - String keyval[] = h.split(":"); - String key = keyval[0].trim().toLowerCase(); - String val = keyval[1].trim(); - if (header.toLowerCase().equals(key)) { - return val; - } - } - } - return null; - } - - public HttpResponseInfo(InputStream is) throws IOException { - IndexInputStream iis = new IndexInputStream(is); - payload_size = iis.readUInt32(); - flags = iis.readInt(); - version = (int) (flags & RESPONSE_INFO_VERSION_MASK); - if (version < RESPONSE_INFO_MINIMUM_VERSION || version > RESPONSE_INFO_VERSION) { - throw new RuntimeException("unexpected response info version: " + version); - } - request_time = iis.readInt64(); - response_time = iis.readInt64(); - String headersStr = iis.readString(); - headers = new ArrayList<>(); - int nulpos; - while ((nulpos = headersStr.indexOf(0)) > 0) { - String h = headersStr.substring(0, nulpos); - headersStr = headersStr.substring(nulpos + 1); - headers.add(h); - } - - //TODO: Read SSL info - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.browsers.cache.chrome; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class HttpResponseInfo { + + public long flags; + + public int version; + + public long request_time; + + public long response_time; + + public long payload_size; + + public List headers; + + // The version of the response info used when persisting response info. + public static final int RESPONSE_INFO_VERSION = 3; + + // The minimum version supported for deserializing response info. + public static final int RESPONSE_INFO_MINIMUM_VERSION = 1; + + // We reserve up to 8 bits for the version number. + public static final int RESPONSE_INFO_VERSION_MASK = 0xFF; + + // This bit is set if the response info has a cert at the end. + // Version 1 serialized only the end-entity certificate, while subsequent + // versions include the available certificate chain. + public static final int RESPONSE_INFO_HAS_CERT = 1 << 8; + + // This bit is set if the response info has a security-bits field (security + // strength, in bits, of the SSL connection) at the end. + public static final int RESPONSE_INFO_HAS_SECURITY_BITS = 1 << 9; + + // This bit is set if the response info has a cert status at the end. + public static final int RESPONSE_INFO_HAS_CERT_STATUS = 1 << 10; + + // This bit is set if the response info has vary header data. + public static final int RESPONSE_INFO_HAS_VARY_DATA = 1 << 11; + + // This bit is set if the request was cancelled before completion. + public static final int RESPONSE_INFO_TRUNCATED = 1 << 12; + + // This bit is set if the response was received via SPDY. + public static final int RESPONSE_INFO_WAS_SPDY = 1 << 13; + + // This bit is set if the request has NPN negotiated. + public static final int RESPONSE_INFO_WAS_NPN = 1 << 14; + + // This bit is set if the request was fetched via an explicit proxy. + public static final int RESPONSE_INFO_WAS_PROXY = 1 << 15; + + // This bit is set if the response info has an SSL connection status field. + // This contains the ciphersuite used to fetch the resource as well as the + // protocol version, compression method and whether SSLv3 fallback was used. + public static final int RESPONSE_INFO_HAS_SSL_CONNECTION_STATUS = 1 << 16; + + // This bit is set if the response info has protocol version. + public static final int RESPONSE_INFO_HAS_NPN_NEGOTIATED_PROTOCOL = 1 << 17; + + // This bit is set if the response info has connection info. + public static final int RESPONSE_INFO_HAS_CONNECTION_INFO = 1 << 18; + + // This bit is set if the request has http authentication. + public static final int RESPONSE_INFO_USE_HTTP_AUTHENTICATION = 1 << 19; + + public String getHeaderValue(String header) { + for (String h : headers) { + if (h.contains(":")) { + String[] keyval = h.split(":"); + String key = keyval[0].trim().toLowerCase(); + String val = keyval[1].trim(); + if (header.toLowerCase().equals(key)) { + return val; + } + } + } + return null; + } + + public HttpResponseInfo(InputStream is) throws IOException { + IndexInputStream iis = new IndexInputStream(is); + payload_size = iis.readUInt32(); + flags = iis.readInt(); + version = (int) (flags & RESPONSE_INFO_VERSION_MASK); + if (version < RESPONSE_INFO_MINIMUM_VERSION || version > RESPONSE_INFO_VERSION) { + throw new RuntimeException("unexpected response info version: " + version); + } + request_time = iis.readInt64(); + response_time = iis.readInt64(); + String headersStr = iis.readString(); + headers = new ArrayList<>(); + int nulpos; + while ((nulpos = headersStr.indexOf(0)) > 0) { + String h = headersStr.substring(0, nulpos); + headersStr = headersStr.substring(nulpos + 1); + headers.add(h); + } + + //TODO: Read SSL info + } +} diff --git a/src/com/jpexs/browsers/cache/chrome/IndexHeader.java b/src/com/jpexs/browsers/cache/chrome/IndexHeader.java index 1981e9aae..645201501 100644 --- a/src/com/jpexs/browsers/cache/chrome/IndexHeader.java +++ b/src/com/jpexs/browsers/cache/chrome/IndexHeader.java @@ -1,76 +1,76 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.browsers.cache.chrome; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; -import java.util.Map; - -/** - * - * @author JPEXS - */ -public class IndexHeader { - - long magic; //c3 ca 03 c1 - - long version; //01 00 02 00 - - int num_entries; - - int num_bytes; - - int last_file; - - int this_id; - - CacheAddr stats; - - int table_len; - - int crash; - - int experiment; - - long create_time; - - int pad[] = new int[52]; - - LruData lru; - - public IndexHeader(InputStream is, File rootDir, Map dataFiles, File externalFilesDir) throws IOException { - IndexInputStream iis = new IndexInputStream(is); - magic = iis.readUInt32(); - version = iis.readUInt32(); - num_entries = iis.readInt32(); - num_bytes = iis.readInt32(); - last_file = iis.readInt32(); - this_id = iis.readInt32(); - stats = new CacheAddr(iis, rootDir, dataFiles, externalFilesDir); - table_len = iis.readInt32(); - crash = iis.readInt32(); - experiment = iis.readInt32(); - create_time = iis.readUInt64(); - pad = new int[52]; - for (int i = 0; i < 52; i++) { - pad[i] = iis.readInt32(); - } - lru = new LruData(is, rootDir, dataFiles, externalFilesDir); - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.browsers.cache.chrome; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.util.Map; + +/** + * + * @author JPEXS + */ +public class IndexHeader { + + long magic; //c3 ca 03 c1 + + long version; //01 00 02 00 + + int num_entries; + + int num_bytes; + + int last_file; + + int this_id; + + CacheAddr stats; + + int table_len; + + int crash; + + int experiment; + + long create_time; + + int[] pad = new int[52]; + + LruData lru; + + public IndexHeader(InputStream is, File rootDir, Map dataFiles, File externalFilesDir) throws IOException { + IndexInputStream iis = new IndexInputStream(is); + magic = iis.readUInt32(); + version = iis.readUInt32(); + num_entries = iis.readInt32(); + num_bytes = iis.readInt32(); + last_file = iis.readInt32(); + this_id = iis.readInt32(); + stats = new CacheAddr(iis, rootDir, dataFiles, externalFilesDir); + table_len = iis.readInt32(); + crash = iis.readInt32(); + experiment = iis.readInt32(); + create_time = iis.readUInt64(); + pad = new int[52]; + for (int i = 0; i < 52; i++) { + pad[i] = iis.readInt32(); + } + lru = new LruData(is, rootDir, dataFiles, externalFilesDir); + } +} diff --git a/src/com/jpexs/browsers/cache/chrome/IndexInputStream.java b/src/com/jpexs/browsers/cache/chrome/IndexInputStream.java index 24ceaa9ee..a3b23b89b 100644 --- a/src/com/jpexs/browsers/cache/chrome/IndexInputStream.java +++ b/src/com/jpexs/browsers/cache/chrome/IndexInputStream.java @@ -1,83 +1,83 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.browsers.cache.chrome; - -import java.io.IOException; -import java.io.InputStream; - -/** - * - * @author JPEXS - */ -public class IndexInputStream extends InputStream { - - private final InputStream is; - - public long pos = 0; - - public long getPos() { - return pos; - } - - public IndexInputStream(InputStream is) { - this.is = is; - } - - @Override - public int read() throws IOException { - int r = is.read(); - //System.out.print("$"+Integer.toHexString(r)); - pos++; - return r; - } - - public long readUInt32() throws IOException { - long r = (((long) read()) + ((long) (read() << 8)) + ((long) (read() << 16)) + ((long) (read() << 24))) & 0xffffffffL; - //System.out.println(""); - return r; - } - - public long readUInt64() throws IOException { - return readUInt32() + (readUInt32() << 32); - } - - public long readInt64() throws IOException { - return readUInt64(); //FIXME - } - - public int readInt32() throws IOException { - return (int) readUInt32(); - } - - public int readInt16() throws IOException { - return (short) ((int) read() + (int) (read() << 8)); - } - - public long readInt() throws IOException { - return readInt32(); - } - - public String readString() throws IOException { - int len = (int) readInt(); - byte data[] = new byte[len]; - if (read(data) != len) { - throw new IOException(); - } - - return new String(data); - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.browsers.cache.chrome; + +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @author JPEXS + */ +public class IndexInputStream extends InputStream { + + private final InputStream is; + + public long pos = 0; + + public long getPos() { + return pos; + } + + public IndexInputStream(InputStream is) { + this.is = is; + } + + @Override + public int read() throws IOException { + int r = is.read(); + //System.out.print("$"+Integer.toHexString(r)); + pos++; + return r; + } + + public long readUInt32() throws IOException { + long r = (((long) read()) + ((long) (read() << 8)) + ((long) (read() << 16)) + ((long) (read() << 24))) & 0xffffffffL; + //System.out.println(""); + return r; + } + + public long readUInt64() throws IOException { + return readUInt32() + (readUInt32() << 32); + } + + public long readInt64() throws IOException { + return readUInt64(); //FIXME + } + + public int readInt32() throws IOException { + return (int) readUInt32(); + } + + public int readInt16() throws IOException { + return (short) ((int) read() + (int) (read() << 8)); + } + + public long readInt() throws IOException { + return readInt32(); + } + + public String readString() throws IOException { + int len = (int) readInt(); + byte[] data = new byte[len]; + if (read(data) != len) { + throw new IOException(); + } + + return new String(data); + } +} diff --git a/src/com/jpexs/browsers/cache/chrome/LruData.java b/src/com/jpexs/browsers/cache/chrome/LruData.java index 53cd725de..5d496c4a1 100644 --- a/src/com/jpexs/browsers/cache/chrome/LruData.java +++ b/src/com/jpexs/browsers/cache/chrome/LruData.java @@ -1,79 +1,79 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.browsers.cache.chrome; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; -import java.util.Map; - -/** - * - * @author JPEXS - */ -public class LruData { - - int pad1[] = new int[2]; - - int filled; - - int sizes[] = new int[5]; - - CacheAddr heads[] = new CacheAddr[5]; - - CacheAddr tails[] = new CacheAddr[5]; - - CacheAddr transaction; - - int operation; - - int operation_list; - - int pad2[] = new int[7]; - - public LruData(InputStream is, File rootDir, Map dataFiles, File externalFilesDir) throws IOException { - IndexInputStream iis = new IndexInputStream(is); - pad1 = new int[2]; - pad1[0] = iis.readInt32(); - pad1[1] = iis.readInt32(); - filled = iis.readInt32(); - sizes = new int[5]; - for (int i = 0; i < 5; i++) { - sizes[i] = iis.readInt32(); - } - sizes = new int[5]; - for (int i = 0; i < 5; i++) { - sizes[i] = iis.readInt32(); - } - heads = new CacheAddr[5]; - for (int i = 0; i < 5; i++) { - heads[i] = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); - } - tails = new CacheAddr[5]; - for (int i = 0; i < 5; i++) { - tails[i] = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); - } - transaction = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); - operation = iis.readInt32(); - operation_list = iis.readInt32(); - pad2 = new int[7]; - for (int i = 0; i < 7; i++) { - pad2[i] = iis.readInt32(); - } - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.browsers.cache.chrome; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.util.Map; + +/** + * + * @author JPEXS + */ +public class LruData { + + int[] pad1 = new int[2]; + + int filled; + + int[] sizes = new int[5]; + + CacheAddr[] heads = new CacheAddr[5]; + + CacheAddr[] tails = new CacheAddr[5]; + + CacheAddr transaction; + + int operation; + + int operation_list; + + int[] pad2 = new int[7]; + + public LruData(InputStream is, File rootDir, Map dataFiles, File externalFilesDir) throws IOException { + IndexInputStream iis = new IndexInputStream(is); + pad1 = new int[2]; + pad1[0] = iis.readInt32(); + pad1[1] = iis.readInt32(); + filled = iis.readInt32(); + sizes = new int[5]; + for (int i = 0; i < 5; i++) { + sizes[i] = iis.readInt32(); + } + sizes = new int[5]; + for (int i = 0; i < 5; i++) { + sizes[i] = iis.readInt32(); + } + heads = new CacheAddr[5]; + for (int i = 0; i < 5; i++) { + heads[i] = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); + } + tails = new CacheAddr[5]; + for (int i = 0; i < 5; i++) { + tails[i] = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); + } + transaction = new CacheAddr(is, rootDir, dataFiles, externalFilesDir); + operation = iis.readInt32(); + operation_list = iis.readInt32(); + pad2 = new int[7]; + for (int i = 0; i < 7; i++) { + pad2[i] = iis.readInt32(); + } + } +} diff --git a/src/com/jpexs/browsers/cache/firefox/FirefoxCache.java b/src/com/jpexs/browsers/cache/firefox/FirefoxCache.java index 6f3eb4658..2f1f47c2d 100644 --- a/src/com/jpexs/browsers/cache/firefox/FirefoxCache.java +++ b/src/com/jpexs/browsers/cache/firefox/FirefoxCache.java @@ -1,167 +1,167 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.browsers.cache.firefox; - -import com.jpexs.browsers.cache.CacheEntry; -import com.jpexs.browsers.cache.CacheImplementation; -import java.io.File; -import java.io.IOException; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -public class FirefoxCache implements CacheImplementation { - - private static volatile FirefoxCache instance; - - private FirefoxCache() { - } - - public static FirefoxCache getInstance() { - if (instance == null) { - synchronized (FirefoxCache.class) { - if (instance == null) { - instance = new FirefoxCache(); - } - } - } - return instance; - } - - private boolean loaded = false; - - private CacheMap map; - - @Override - public void refresh() { - File dir = getCacheDirectory(); - if (dir == null) { - return; - } - File cacheMapFile = new File(dir, "_CACHE_MAP_"); - try { - map = new CacheMap(cacheMapFile); - } catch (IOException ex) { - Logger.getLogger(FirefoxCache.class.getName()).log(Level.SEVERE, null, ex); - } - loaded = true; - } - - @Override - public List getEntries() { - if (!loaded) { - refresh(); - } - if (map == null) { - return null; - } - List ret = new ArrayList<>(); - - ret.addAll(map.mapBuckets); - return ret; - } - - private enum OSId { - - WINDOWS, OSX, UNIX - } - - private static OSId getOSId() { - PrivilegedAction doGetOSName = new PrivilegedAction() { - @Override - public String run() { - return System.getProperty("os.name"); - } - }; - OSId id = OSId.UNIX; - String osName = AccessController.doPrivileged(doGetOSName); - if (osName != null) { - if (osName.toLowerCase().startsWith("mac os x")) { - id = OSId.OSX; - } else if (osName.contains("Windows")) { - id = OSId.WINDOWS; - } - } - return id; - } - - public static File getProfileDirectory() { - File profilesDir = getProfilesDirectory(); - if (profilesDir == null) { - return null; - } - File profiles[] = profilesDir.listFiles(); - File profileDir = null; - for (File f : profiles) { - if (f.isDirectory()) { - if (f.getName().matches("[a-z0-9]+\\.default")) { - profileDir = f; - break; - } - } - } - return profileDir; - } - - public static File getCacheDirectory() { - File profileDir = getProfileDirectory(); - File cacheDir = null; - if (profileDir != null) { - cacheDir = new File(profileDir, "Cache"); - } - if (cacheDir == null) { - return null; - } - if (!cacheDir.exists()) { - return null; - } - return cacheDir; - } - - public static File getProfilesDirectory() { - String userHome = null; - File profilesDir = null; - try { - userHome = System.getProperty("user.home"); - } catch (SecurityException ignore) { - } - if (userHome != null) { - OSId osId = getOSId(); - if (osId == OSId.WINDOWS) { - profilesDir = new File(userHome + "\\AppData\\Local\\Mozilla\\Firefox\\Profiles"); - if (!profilesDir.exists()) { - profilesDir = new File(userHome + "\\Local Settings\\Application Data\\Mozilla\\Firefox\\Profiles"); - } - } else if (osId == OSId.OSX) { - profilesDir = new File(userHome + "/Library/Caches/Firefox/Profiles"); - } else { - profilesDir = new File(userHome + "/.mozilla/firefox"); - } - } - if ((profilesDir == null) || !profilesDir.exists()) { - return null; - } - return profilesDir; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.browsers.cache.firefox; + +import com.jpexs.browsers.cache.CacheEntry; +import com.jpexs.browsers.cache.CacheImplementation; +import java.io.File; +import java.io.IOException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class FirefoxCache implements CacheImplementation { + + private static volatile FirefoxCache instance; + + private FirefoxCache() { + } + + public static FirefoxCache getInstance() { + if (instance == null) { + synchronized (FirefoxCache.class) { + if (instance == null) { + instance = new FirefoxCache(); + } + } + } + return instance; + } + + private boolean loaded = false; + + private CacheMap map; + + @Override + public void refresh() { + File dir = getCacheDirectory(); + if (dir == null) { + return; + } + File cacheMapFile = new File(dir, "_CACHE_MAP_"); + try { + map = new CacheMap(cacheMapFile); + } catch (IOException ex) { + Logger.getLogger(FirefoxCache.class.getName()).log(Level.SEVERE, null, ex); + } + loaded = true; + } + + @Override + public List getEntries() { + if (!loaded) { + refresh(); + } + if (map == null) { + return null; + } + List ret = new ArrayList<>(); + + ret.addAll(map.mapBuckets); + return ret; + } + + private enum OSId { + + WINDOWS, OSX, UNIX + } + + private static OSId getOSId() { + PrivilegedAction doGetOSName = new PrivilegedAction() { + @Override + public String run() { + return System.getProperty("os.name"); + } + }; + OSId id = OSId.UNIX; + String osName = AccessController.doPrivileged(doGetOSName); + if (osName != null) { + if (osName.toLowerCase().startsWith("mac os x")) { + id = OSId.OSX; + } else if (osName.contains("Windows")) { + id = OSId.WINDOWS; + } + } + return id; + } + + public static File getProfileDirectory() { + File profilesDir = getProfilesDirectory(); + if (profilesDir == null) { + return null; + } + File[] profiles = profilesDir.listFiles(); + File profileDir = null; + for (File f : profiles) { + if (f.isDirectory()) { + if (f.getName().matches("[a-z0-9]+\\.default")) { + profileDir = f; + break; + } + } + } + return profileDir; + } + + public static File getCacheDirectory() { + File profileDir = getProfileDirectory(); + File cacheDir = null; + if (profileDir != null) { + cacheDir = new File(profileDir, "Cache"); + } + if (cacheDir == null) { + return null; + } + if (!cacheDir.exists()) { + return null; + } + return cacheDir; + } + + public static File getProfilesDirectory() { + String userHome = null; + File profilesDir = null; + try { + userHome = System.getProperty("user.home"); + } catch (SecurityException ignore) { + } + if (userHome != null) { + OSId osId = getOSId(); + if (osId == OSId.WINDOWS) { + profilesDir = new File(userHome + "\\AppData\\Local\\Mozilla\\Firefox\\Profiles"); + if (!profilesDir.exists()) { + profilesDir = new File(userHome + "\\Local Settings\\Application Data\\Mozilla\\Firefox\\Profiles"); + } + } else if (osId == OSId.OSX) { + profilesDir = new File(userHome + "/Library/Caches/Firefox/Profiles"); + } else { + profilesDir = new File(userHome + "/.mozilla/firefox"); + } + } + if ((profilesDir == null) || !profilesDir.exists()) { + return null; + } + return profilesDir; + } +} diff --git a/src/com/jpexs/browsers/cache/firefox/MapBucket.java b/src/com/jpexs/browsers/cache/firefox/MapBucket.java index 2538eb733..0dc29bb68 100644 --- a/src/com/jpexs/browsers/cache/firefox/MapBucket.java +++ b/src/com/jpexs/browsers/cache/firefox/MapBucket.java @@ -1,132 +1,132 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.browsers.cache.firefox; - -import com.jpexs.browsers.cache.CacheEntry; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -public class MapBucket extends CacheEntry { - - public long hash; - - public long enviction; - - public Location dataLocation; - - public Location metadataLocation; - - private MetaData metadata; - - public MapBucket(InputStream is, File rootDir, Map dataFiles) throws IOException { - CacheInputStream cis = new CacheInputStream(is); - hash = cis.readInt32(); - enviction = cis.readInt32(); - dataLocation = new Location(cis.readInt32(), false, hash, rootDir, dataFiles); - metadataLocation = new Location(cis.readInt32(), true, hash, rootDir, dataFiles); - } - - public InputStream getMetaDataStream() throws IOException { - return metadataLocation.getInputStream(); - } - - public MetaData getMetaData() { - if (metadata == null) { - try { - metadata = new MetaData(getMetaDataStream()); - } catch (IncompatibleVersionException ie) { - } catch (IOException ex) { - Logger.getLogger(MapBucket.class.getName()).log(Level.SEVERE, null, ex); - } - } - return metadata; - } - - @Override - public String getRequestURL() { - MetaData m = getMetaData(); - if (m == null) { - return null; - } - String req = m.request; - if (req == null) { - return null; - } - if (req.startsWith("HTTP:")) { - req = req.substring("HTTP:".length()); - } - return req; - } - - @Override - public Map getResponseHeaders() { - MetaData m = getMetaData(); - if (m == null) { - return null; - } - String responseHead = m.response.get("response-head"); - if (responseHead == null) { - return null; - } - 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(":"); - ret.put(hp[0].trim(), hp[1].trim()); - } - } - return ret; - } - - @Override - public String getStatusLine() { - MetaData m = getMetaData(); - if (m == null) { - return null; - } - String responseHead = m.response.get("response-head"); - String headers[] = responseHead.split("\r\n"); - return headers[0]; - } - - @Override - public String getRequestMethod() { - return "GET"; //No POST caching in Firefox - } - - @Override - public InputStream getResponseRawDataStream() { - try { - return dataLocation.getInputStream(); - } catch (IOException ex) { - Logger.getLogger(MapBucket.class.getName()).log(Level.SEVERE, null, ex); - } - return null; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.browsers.cache.firefox; + +import com.jpexs.browsers.cache.CacheEntry; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class MapBucket extends CacheEntry { + + public long hash; + + public long enviction; + + public Location dataLocation; + + public Location metadataLocation; + + private MetaData metadata; + + public MapBucket(InputStream is, File rootDir, Map dataFiles) throws IOException { + CacheInputStream cis = new CacheInputStream(is); + hash = cis.readInt32(); + enviction = cis.readInt32(); + dataLocation = new Location(cis.readInt32(), false, hash, rootDir, dataFiles); + metadataLocation = new Location(cis.readInt32(), true, hash, rootDir, dataFiles); + } + + public InputStream getMetaDataStream() throws IOException { + return metadataLocation.getInputStream(); + } + + public MetaData getMetaData() { + if (metadata == null) { + try { + metadata = new MetaData(getMetaDataStream()); + } catch (IncompatibleVersionException ie) { + } catch (IOException ex) { + Logger.getLogger(MapBucket.class.getName()).log(Level.SEVERE, null, ex); + } + } + return metadata; + } + + @Override + public String getRequestURL() { + MetaData m = getMetaData(); + if (m == null) { + return null; + } + String req = m.request; + if (req == null) { + return null; + } + if (req.startsWith("HTTP:")) { + req = req.substring("HTTP:".length()); + } + return req; + } + + @Override + public Map getResponseHeaders() { + MetaData m = getMetaData(); + if (m == null) { + return null; + } + String responseHead = m.response.get("response-head"); + if (responseHead == null) { + return null; + } + 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(":"); + ret.put(hp[0].trim(), hp[1].trim()); + } + } + return ret; + } + + @Override + public String getStatusLine() { + MetaData m = getMetaData(); + if (m == null) { + return null; + } + String responseHead = m.response.get("response-head"); + String[] headers = responseHead.split("\r\n"); + return headers[0]; + } + + @Override + public String getRequestMethod() { + return "GET"; //No POST caching in Firefox + } + + @Override + public InputStream getResponseRawDataStream() { + try { + return dataLocation.getInputStream(); + } catch (IOException ex) { + Logger.getLogger(MapBucket.class.getName()).log(Level.SEVERE, null, ex); + } + return null; + } +} diff --git a/src/com/jpexs/browsers/cache/firefox/MetaData.java b/src/com/jpexs/browsers/cache/firefox/MetaData.java index 9c0fd0f96..32f9235f1 100644 --- a/src/com/jpexs/browsers/cache/firefox/MetaData.java +++ b/src/com/jpexs/browsers/cache/firefox/MetaData.java @@ -1,93 +1,93 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.browsers.cache.firefox; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; - -/** - * - * @author JPEXS - */ -public class MetaData { - - public int majorVersion; - - public int minorVersion; - - public long location; - - public long fetchCount; - - public long firstFetchTime; - - public long lastFetchTime; - - public long expireTime; - - public long dataSize; - - public long requestSize; - - public long infoSize; - - public String request; - - public Map response; - - public MetaData(InputStream is) throws IOException, IncompatibleVersionException { - CacheInputStream cis = new CacheInputStream(is); - majorVersion = cis.readInt16(); - if (majorVersion != 1) { - throw new IncompatibleVersionException(majorVersion); - } - minorVersion = cis.readInt16(); - location = cis.readInt32(); - fetchCount = cis.readInt32(); - firstFetchTime = cis.readInt32(); - lastFetchTime = cis.readInt32(); - expireTime = cis.readInt32(); - dataSize = cis.readInt32(); - requestSize = cis.readInt32(); - infoSize = cis.readInt32(); - 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]; - cis.read(res); - String responseStr = new String(res); - int nulpos; - boolean inKey = true; - String key = null; - response = new HashMap<>(); - while ((nulpos = responseStr.indexOf(0)) > 0) { - String v = responseStr.substring(0, nulpos); - responseStr = responseStr.substring(nulpos + 1); - if (inKey) { - key = v; - } else { - response.put(key, v); - } - inKey = !inKey; - } - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.browsers.cache.firefox; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author JPEXS + */ +public class MetaData { + + public int majorVersion; + + public int minorVersion; + + public long location; + + public long fetchCount; + + public long firstFetchTime; + + public long lastFetchTime; + + public long expireTime; + + public long dataSize; + + public long requestSize; + + public long infoSize; + + public String request; + + public Map response; + + public MetaData(InputStream is) throws IOException, IncompatibleVersionException { + CacheInputStream cis = new CacheInputStream(is); + majorVersion = cis.readInt16(); + if (majorVersion != 1) { + throw new IncompatibleVersionException(majorVersion); + } + minorVersion = cis.readInt16(); + location = cis.readInt32(); + fetchCount = cis.readInt32(); + firstFetchTime = cis.readInt32(); + lastFetchTime = cis.readInt32(); + expireTime = cis.readInt32(); + dataSize = cis.readInt32(); + requestSize = cis.readInt32(); + infoSize = cis.readInt32(); + 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]; + cis.read(res); + String responseStr = new String(res); + int nulpos; + boolean inKey = true; + String key = null; + response = new HashMap<>(); + while ((nulpos = responseStr.indexOf(0)) > 0) { + String v = responseStr.substring(0, nulpos); + responseStr = responseStr.substring(nulpos + 1); + if (inKey) { + key = v; + } else { + response.put(key, v); + } + inKey = !inKey; + } + } +} diff --git a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java index 906c93de2..b64c03838 100644 --- a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java +++ b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java @@ -1532,7 +1532,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..32a61e6a2 100644 --- a/src/com/jpexs/decompiler/flash/console/ContextMenuTools.java +++ b/src/com/jpexs/decompiler/flash/console/ContextMenuTools.java @@ -1,235 +1,235 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.console; - -import com.jpexs.helpers.utf8.Utf8Helper; -import com.sun.jna.Platform; -import com.sun.jna.WString; -import com.sun.jna.platform.win32.Advapi32Util; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.SHELLEXECUTEINFO; -import com.sun.jna.platform.win32.Shell32; -import com.sun.jna.platform.win32.Win32Exception; -import com.sun.jna.platform.win32.WinReg; -import com.sun.jna.platform.win32.WinUser; -import java.io.File; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -public class ContextMenuTools { - - public static String getAppDir() { - String path = Utf8Helper.urlDecode(ContextMenuTools.class.getProtectionDomain().getCodeSource().getLocation().getPath()); - String appDir = new File(path).getParentFile().getAbsolutePath(); - if (!appDir.endsWith("\\")) { - appDir += "\\"; - } - return appDir; - } - - public static boolean isAddedToContextMenu() { - if (!Platform.isWindows()) { - return false; - } - final WinReg.HKEY REG_CLASSES_HKEY = WinReg.HKEY_LOCAL_MACHINE; - final String REG_CLASSES_PATH = "Software\\Classes\\"; - try { - if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + ".swf")) { - return false; - } - String clsName = Advapi32Util.registryGetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + ".swf", ""); - if (clsName == null) { - return false; - } - return Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\ffdec"); - } catch (Win32Exception ex) { - return false; - } - } - - public static boolean addToContextMenu(boolean add, boolean fromCommandLine) { - if (add == isAddedToContextMenu()) { - return true; - } - - String exeName = "ffdec.exe"; - - if (add) { - return addToContextMenu(add, fromCommandLine, exeName); - } else { - // remove both 32 and 64 bit references - return addToContextMenu(add, fromCommandLine, exeName) - && addToContextMenu(add, fromCommandLine, "ffdec64.exe"); //remove 64 exe from previous versions - } - } - - private static boolean addToContextMenu(boolean add, boolean fromCommandLine, String exeName) { - final String extensions[] = new String[]{"swf", "gfx"}; - - final WinReg.HKEY REG_CLASSES_HKEY = WinReg.HKEY_LOCAL_MACHINE; - final String REG_CLASSES_PATH = "Software\\Classes\\"; - - String appDir = getAppDir(); - String verb = "ffdec"; - String verbName = "Open with FFDec"; - boolean exists; - try { - - exists = Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName); - if ((!exists) && add) { //add - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName); - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell"); - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open"); - Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open", "", verbName); - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open\\command"); - Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open\\command", "", "\"" + appDir + exeName + "\" \"%1\""); - - } - - for (String ext : extensions) { - - // 1) Add to context menu of SWF - if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "." + ext)) { - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "." + ext); - Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "." + ext, "", "ShockwaveFlash.ShockwaveFlash"); - } - - if (Advapi32Util.registryValueExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "." + ext, "")) { - String clsName = Advapi32Util.registryGetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "." + ext, ""); - if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName)) { - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName); - Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName, "", "Flash Movie"); - } - - if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell")) { - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell"); - } - - exists = Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb); - - if ((!exists) && add) { //add - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb); - Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb, "", verbName); - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb + "\\command"); - Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb + "\\command", "", "\"" + appDir + exeName + "\" \"%1\""); - } - if (exists && (!add)) { //remove - registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb + "\\command"); - registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb); - } - } - - exists = Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName); - - if (exists && (!add)) { //remove - registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open\\command"); - registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open"); - registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell"); - registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName); - } - //2) Add to OpenWith list - if (Advapi32Util.registryValueExists(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "MRUList")) { - String mruList = Advapi32Util.registryGetStringValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "MRUList"); - if (mruList != null) { - exists = false; - char appChar = 0; - for (int i = 0; i < mruList.length(); i++) { - String app = Advapi32Util.registryGetStringValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "" + mruList.charAt(i)); - if (app.equals(exeName)) { - appChar = mruList.charAt(i); - exists = true; - break; - } - } - if ((!exists) && add) { //add - for (int c = 'a'; c <= 'z'; c++) { - if (mruList.indexOf(c) == -1) { - mruList += (char) c; - Advapi32Util.registrySetStringValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "" + (char) c, exeName); - Advapi32Util.registrySetStringValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "MRUList", mruList); - break; - } - } - } - if (exists && (!add)) { //remove - mruList = mruList.replace("" + appChar, ""); - Advapi32Util.registrySetStringValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "MRUList", mruList); - registryDeleteValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "" + appChar); - } - } - } - - //On some systems, file must be associated in SystemFileAssociations too - if (Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations")) { - exists = Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb); - if ((!exists) && add) { //add - if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "")) { - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + ""); - } - if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell")) { - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell"); - } - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb); - Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb, "", verbName); - Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb + "\\Command"); - Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb + "\\Command", "", "\"" + appDir + exeName + "\" \"%1\""); - } - if (exists && (!add)) { //remove - registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb + "\\Command"); - registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb); - } - } - } - return true; - } catch (Exception ex) { - if (!fromCommandLine) { - //Updating registry failed, try elevating rights - SHELLEXECUTEINFO sei = new SHELLEXECUTEINFO(); - sei.fMask = 0x00000040; - sei.lpVerb = new WString("runas"); - sei.lpFile = new WString(appDir + exeName); - sei.lpParameters = new WString(add ? "-addtocontextmenu" : "-removefromcontextmenu"); - sei.nShow = WinUser.SW_NORMAL; - Shell32.INSTANCE.ShellExecuteEx(sei); - //Wait till exit - Kernel32.INSTANCE.WaitForSingleObject(sei.hProcess, 1000 * 60 * 60 * 24 /*1 day max*/); - Kernel32.INSTANCE.CloseHandle(sei.hProcess); - } else { - Logger.getLogger(ContextMenuTools.class.getName()).log(Level.SEVERE, null, ex); - } - } - return false; - } - - private static void registryDeleteKey(WinReg.HKEY hKey, String keyName) { - boolean exists = Advapi32Util.registryKeyExists(hKey, keyName); - if (exists) { - Advapi32Util.registryDeleteKey(hKey, keyName); - } - } - - private static void registryDeleteValue(WinReg.HKEY root, String keyPath, String valueName) { - boolean exists = Advapi32Util.registryValueExists(root, keyPath, valueName); - if (exists) { - Advapi32Util.registryDeleteValue(root, keyPath, valueName); - } - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.console; + +import com.jpexs.helpers.utf8.Utf8Helper; +import com.sun.jna.Platform; +import com.sun.jna.WString; +import com.sun.jna.platform.win32.Advapi32Util; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.SHELLEXECUTEINFO; +import com.sun.jna.platform.win32.Shell32; +import com.sun.jna.platform.win32.Win32Exception; +import com.sun.jna.platform.win32.WinReg; +import com.sun.jna.platform.win32.WinUser; +import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class ContextMenuTools { + + public static String getAppDir() { + String path = Utf8Helper.urlDecode(ContextMenuTools.class.getProtectionDomain().getCodeSource().getLocation().getPath()); + String appDir = new File(path).getParentFile().getAbsolutePath(); + if (!appDir.endsWith("\\")) { + appDir += "\\"; + } + return appDir; + } + + public static boolean isAddedToContextMenu() { + if (!Platform.isWindows()) { + return false; + } + final WinReg.HKEY REG_CLASSES_HKEY = WinReg.HKEY_LOCAL_MACHINE; + final String REG_CLASSES_PATH = "Software\\Classes\\"; + try { + if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + ".swf")) { + return false; + } + String clsName = Advapi32Util.registryGetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + ".swf", ""); + if (clsName == null) { + return false; + } + return Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\ffdec"); + } catch (Win32Exception ex) { + return false; + } + } + + public static boolean addToContextMenu(boolean add, boolean fromCommandLine) { + if (add == isAddedToContextMenu()) { + return true; + } + + String exeName = "ffdec.exe"; + + if (add) { + return addToContextMenu(add, fromCommandLine, exeName); + } else { + // remove both 32 and 64 bit references + return addToContextMenu(add, fromCommandLine, exeName) + && addToContextMenu(add, fromCommandLine, "ffdec64.exe"); //remove 64 exe from previous versions + } + } + + private static boolean addToContextMenu(boolean add, boolean fromCommandLine, String exeName) { + final String[] extensions = new String[]{"swf", "gfx"}; + + final WinReg.HKEY REG_CLASSES_HKEY = WinReg.HKEY_LOCAL_MACHINE; + final String REG_CLASSES_PATH = "Software\\Classes\\"; + + String appDir = getAppDir(); + String verb = "ffdec"; + String verbName = "Open with FFDec"; + boolean exists; + try { + + exists = Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName); + if ((!exists) && add) { //add + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName); + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell"); + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open"); + Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open", "", verbName); + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open\\command"); + Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open\\command", "", "\"" + appDir + exeName + "\" \"%1\""); + + } + + for (String ext : extensions) { + + // 1) Add to context menu of SWF + if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "." + ext)) { + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "." + ext); + Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "." + ext, "", "ShockwaveFlash.ShockwaveFlash"); + } + + if (Advapi32Util.registryValueExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "." + ext, "")) { + String clsName = Advapi32Util.registryGetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "." + ext, ""); + if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName)) { + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName); + Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName, "", "Flash Movie"); + } + + if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell")) { + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell"); + } + + exists = Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb); + + if ((!exists) && add) { //add + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb); + Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb, "", verbName); + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb + "\\command"); + Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb + "\\command", "", "\"" + appDir + exeName + "\" \"%1\""); + } + if (exists && (!add)) { //remove + registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb + "\\command"); + registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + clsName + "\\shell\\" + verb); + } + } + + exists = Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName); + + if (exists && (!add)) { //remove + registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open\\command"); + registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell\\open"); + registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName + "\\shell"); + registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "Applications\\" + exeName); + } + //2) Add to OpenWith list + if (Advapi32Util.registryValueExists(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "MRUList")) { + String mruList = Advapi32Util.registryGetStringValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "MRUList"); + if (mruList != null) { + exists = false; + char appChar = 0; + for (int i = 0; i < mruList.length(); i++) { + String app = Advapi32Util.registryGetStringValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "" + mruList.charAt(i)); + if (app.equals(exeName)) { + appChar = mruList.charAt(i); + exists = true; + break; + } + } + if ((!exists) && add) { //add + for (int c = 'a'; c <= 'z'; c++) { + if (mruList.indexOf(c) == -1) { + mruList += (char) c; + Advapi32Util.registrySetStringValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "" + (char) c, exeName); + Advapi32Util.registrySetStringValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "MRUList", mruList); + break; + } + } + } + if (exists && (!add)) { //remove + mruList = mruList.replace("" + appChar, ""); + Advapi32Util.registrySetStringValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "MRUList", mruList); + registryDeleteValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + ext + "\\OpenWithList", "" + appChar); + } + } + } + + //On some systems, file must be associated in SystemFileAssociations too + if (Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations")) { + exists = Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb); + if ((!exists) && add) { //add + if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "")) { + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + ""); + } + if (!Advapi32Util.registryKeyExists(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell")) { + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell"); + } + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb); + Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb, "", verbName); + Advapi32Util.registryCreateKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb + "\\Command"); + Advapi32Util.registrySetStringValue(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb + "\\Command", "", "\"" + appDir + exeName + "\" \"%1\""); + } + if (exists && (!add)) { //remove + registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb + "\\Command"); + registryDeleteKey(REG_CLASSES_HKEY, REG_CLASSES_PATH + "SystemFileAssociations\\." + ext + "\\Shell\\" + verb); + } + } + } + return true; + } catch (Exception ex) { + if (!fromCommandLine) { + //Updating registry failed, try elevating rights + SHELLEXECUTEINFO sei = new SHELLEXECUTEINFO(); + sei.fMask = 0x00000040; + sei.lpVerb = new WString("runas"); + sei.lpFile = new WString(appDir + exeName); + sei.lpParameters = new WString(add ? "-addtocontextmenu" : "-removefromcontextmenu"); + sei.nShow = WinUser.SW_NORMAL; + Shell32.INSTANCE.ShellExecuteEx(sei); + //Wait till exit + Kernel32.INSTANCE.WaitForSingleObject(sei.hProcess, 1000 * 60 * 60 * 24 /*1 day max*/); + Kernel32.INSTANCE.CloseHandle(sei.hProcess); + } else { + Logger.getLogger(ContextMenuTools.class.getName()).log(Level.SEVERE, null, ex); + } + } + return false; + } + + private static void registryDeleteKey(WinReg.HKEY hKey, String keyName) { + boolean exists = Advapi32Util.registryKeyExists(hKey, keyName); + if (exists) { + Advapi32Util.registryDeleteKey(hKey, keyName); + } + } + + private static void registryDeleteValue(WinReg.HKEY root, String keyPath, String valueName) { + boolean exists = Advapi32Util.registryValueExists(root, keyPath, valueName); + if (exists) { + Advapi32Util.registryDeleteValue(root, keyPath, valueName); + } + } +} 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..2c223c83d 100644 --- a/src/com/jpexs/decompiler/flash/gui/DebugPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/DebugPanel.java @@ -1,508 +1,508 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui; - -import com.jpexs.debugger.flash.Variable; -import com.jpexs.debugger.flash.messages.in.InBreakAtExt; -import com.jpexs.debugger.flash.messages.in.InConstantPool; -import com.jpexs.debugger.flash.messages.in.InFrame; -import com.jpexs.decompiler.flash.gui.DebuggerHandler.BreakListener; -import com.jpexs.decompiler.flash.gui.abc.ABCPanel; -import de.hameister.treetable.MyTreeTable; -import de.hameister.treetable.MyTreeTableModel; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.FlowLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.List; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTabbedPane; -import javax.swing.JTable; -import javax.swing.JTextArea; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.TreeModelEvent; -import javax.swing.event.TreeModelListener; -import javax.swing.table.DefaultTableModel; -import javax.swing.tree.TreePath; - -/** - * - * @author JPEXS - */ -public class DebugPanel extends JPanel { - - private MyTreeTable debugRegistersTable; - - private MyTreeTable debugLocalsTable; //JTable debugLocalsTable; - - private MyTreeTable debugScopeTable; - - private JTable callStackTable; - - private JTable stackTable; - - private JTable constantPoolTable; - - private JTabbedPane varTabs; - - private BreakListener listener; - - private JTextArea traceLogTextarea; - - private int logLength = 0; - - private List tabTypes = new ArrayList<>(); - - private boolean loading = false; - - public static enum SelectedTab { - - LOG, STACK, SCOPECHAIN, LOCALS, REGISTERS, CALLSTACK, CONSTANTPOOL - } - - public synchronized boolean isLoading() { - return loading; - } - - public synchronized void setLoading(boolean loading) { - this.loading = loading; - } - - private SelectedTab selectedTab = null; - - private void safeSetTreeModel(MyTreeTable tt, MyTreeTableModel tmodel) { - List> expanded = View.getExpandedNodes(tt.getTree()); - - int selRows[] = tt.getSelectedRows(); - - TreePath selPaths[] = new TreePath[selRows.length]; - for (int i = 0; i < selRows.length; i++) { - selPaths[i] = tt.getTree().getPathForRow(selRows[i]); - } - tt.setTreeModel(tmodel); - //tt.getTree().setRootVisible(false); - - View.expandTreeNodes(tt.getTree(), expanded); - for (int i = 0; i < selRows.length; i++) { - selRows[i] = tt.getTree().getRowForPath(selPaths[i]); - if (i == 0) { - tt.setRowSelectionInterval(selRows[i], selRows[i]); - } else { - tt.addRowSelectionInterval(selRows[i], selRows[i]); - } - } - - } - - public DebugPanel() { - super(new BorderLayout()); - debugRegistersTable = new MyTreeTable(new ABCPanel.VariablesTableModel(debugRegistersTable, new ArrayList<>(), new ArrayList<>()), false); - debugLocalsTable = new MyTreeTable(new ABCPanel.VariablesTableModel(debugLocalsTable, new ArrayList<>(), new ArrayList<>()), false); - - //Add watch feature, commented out. I tried it, but without success. I can't add watch in Flash Pro or FDB either. :-( - /* - //locales - debug.watch.add = Add watch to %name% - debug.watch.add.read = Read - debug.watch.add.write = Write - debug.watch.add.readwrite = Read+Write - - error.debug.watch.add = Cannot add watch to this variable. - - - MouseAdapter watchHandler = new MouseAdapter() { - - @Override - public void mousePressed(MouseEvent e) { - if (e.isPopupTrigger()) { - dopop(e); - } - } - - @Override - public void mouseReleased(MouseEvent e) { - if (e.isPopupTrigger()) { - dopop(e); - } - } - - private void dopop(MouseEvent e) { - if (debugLocalsTable.getSelectedRow() == -1) { - return; - } - Variable v = ((ABCPanel.VariablesTableModel) debugLocalsTable.getModel()).getVars().get(debugLocalsTable.getSelectedRow()); - final long v_id = ((ABCPanel.VariablesTableModel) debugLocalsTable.getModel()).getVarIds().get(debugLocalsTable.getSelectedRow()); - - JPopupMenu pm = new JPopupMenu(); - JMenu addWatchMenu = new JMenu(AppStrings.translate("debug.watch.add").replace("%name%", v.name)); - JMenuItem watchReadMenuItem = new JMenuItem(AppStrings.translate("debug.watch.add.read")); - watchReadMenuItem.addActionListener((ActionEvent e1) -> { - if (!Main.addWatch(v, v_id, true, false)) { - View.showMessageDialog(DebugPanel.this, AppStrings.translate("debug.watch.add"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } - }); - JMenuItem watchWriteMenuItem = new JMenuItem(AppStrings.translate("debug.watch.add.write")); - watchWriteMenuItem.addActionListener((ActionEvent e1) -> { - if (!Main.addWatch(v, v_id, false, true)) { - View.showMessageDialog(DebugPanel.this, AppStrings.translate("debug.watch.add"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } - }); - JMenuItem watchReadWriteMenuItem = new JMenuItem(AppStrings.translate("debug.watch.add.readwrite")); - watchReadWriteMenuItem.addActionListener((ActionEvent e1) -> { - if (!Main.addWatch(v, v_id, true, true)) { - View.showMessageDialog(DebugPanel.this, AppStrings.translate("debug.watch.add"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } - }); - - addWatchMenu.add(watchReadMenuItem); - addWatchMenu.add(watchWriteMenuItem); - addWatchMenu.add(watchReadWriteMenuItem); - pm.add(addWatchMenu); - - pm.show(e.getComponent(), e.getX(), e.getY()); - } - }; - - debugLocalsTable.addMouseListener(watchHandler); - debugScopeTable.addMouseListener(watchHandler); - - */ - debugScopeTable = new MyTreeTable(new ABCPanel.VariablesTableModel(debugScopeTable, new ArrayList<>(), new ArrayList<>()), false); - - callStackTable = new JTable(); - stackTable = new JTable(); - constantPoolTable = new JTable(); - traceLogTextarea = new JTextArea(); - traceLogTextarea.setEditable(false); - traceLogTextarea.setOpaque(false); - traceLogTextarea.setFont(new JLabel().getFont()); - traceLogTextarea.setBackground(Color.white); - - Main.getDebugHandler().addTraceListener(new DebuggerHandler.TraceListener() { - - @Override - public void trace(String... val) { - for (String s : val) { - String add = "trace: " + s + "\r\n"; - boolean wasEmpty = logLength == 0; - logLength += add.length(); - traceLogTextarea.append(add); - try { - traceLogTextarea.setCaretPosition(logLength); - } catch (IllegalArgumentException iex) { - //ignore - } - if (wasEmpty) { - refresh(); - } - } - } - }); - - Main.getDebugHandler().addConnectionListener(new DebuggerHandler.ConnectionListener() { - - @Override - public void connected() { - } - - @Override - public void disconnected() { - refresh(); - } - }); - - Main.getDebugHandler().addBreakListener(listener = new DebuggerHandler.BreakListener() { - - @Override - public void doContinue() { - View.execInEventDispatch(new Runnable() { - - @Override - public void run() { - refresh(); - } - - }); - } - - @Override - public void breakAt(String scriptName, int line, int classIndex, int traitIndex, int methodIndex) { - View.execInEventDispatch(new Runnable() { - - @Override - public void run() { - refresh(); - } - }); - - } - }); - - varTabs = new JTabbedPane(); - varTabs.addChangeListener(new ChangeListener() { - - @Override - public void stateChanged(ChangeEvent e) { - if (e.getSource() == varTabs) { - if (isLoading()) { - return; - } - synchronized (DebugPanel.this) { - int si = varTabs.getSelectedIndex(); - if (si > -1 && si < tabTypes.size()) { - selectedTab = tabTypes.get(si); - } - } - } - } - }); - - add(new HeaderLabel(AppStrings.translate("debugpanel.header")), BorderLayout.NORTH); - add(varTabs, BorderLayout.CENTER); - } - - public void refresh() { - - View.execInEventDispatch(new Runnable() { - - @Override - public void run() { - setLoading(true); - synchronized (DebugPanel.this) { - - SelectedTab oldSel = selectedTab; - InFrame f = Main.getDebugHandler().getFrame(); - if (f != null) { - - List regVarIds = new ArrayList<>(); - for (int i = 0; i < f.registers.size(); i++) { - regVarIds.add(0L); - } - safeSetTreeModel(debugRegistersTable, new ABCPanel.VariablesTableModel(debugRegistersTable, f.registers, regVarIds)); - List locals = new ArrayList<>(); - locals.addAll(f.arguments); - locals.addAll(f.variables); - - List localIds = new ArrayList<>(); - localIds.addAll(f.argumentFrameIds); - localIds.addAll(f.frameIds); - - safeSetTreeModel(debugLocalsTable, new ABCPanel.VariablesTableModel(debugLocalsTable, locals, localIds)); - safeSetTreeModel(debugScopeTable, new ABCPanel.VariablesTableModel(debugScopeTable, f.scopeChain, f.scopeChainFrameIds)); - - /*TableModelListener refreshListener = new TableModelListener() { - @Override - public void tableChanged(TableModelEvent e) { - Main.getDebugHandler().refreshFrame(); - refresh(); - } - };*/ - TreeModelListener refreshListener = new TreeModelListener() { - @Override - public void treeNodesChanged(TreeModelEvent e) { - Main.getDebugHandler().refreshFrame(); - refresh(); - } - - @Override - public void treeNodesInserted(TreeModelEvent e) { - Main.getDebugHandler().refreshFrame(); - refresh(); - } - - @Override - public void treeNodesRemoved(TreeModelEvent e) { - Main.getDebugHandler().refreshFrame(); - refresh(); - } - - @Override - public void treeStructureChanged(TreeModelEvent e) { - Main.getDebugHandler().refreshFrame(); - refresh(); - } - }; - debugLocalsTable.getTreeTableModel().addTreeModelListener(refreshListener); - debugScopeTable.getTreeTableModel().addTreeModelListener(refreshListener); - } else { - debugRegistersTable.setTreeModel(new ABCPanel.VariablesTableModel(debugRegistersTable, new ArrayList<>(), new ArrayList<>())); - debugLocalsTable.setTreeModel(new ABCPanel.VariablesTableModel(debugLocalsTable, new ArrayList<>(), new ArrayList<>())); - debugScopeTable.setTreeModel(new ABCPanel.VariablesTableModel(debugScopeTable, new ArrayList<>(), new ArrayList<>())); - } - InBreakAtExt info = Main.getDebugHandler().getBreakInfo(); - if (info != null) { - //InBreakReason reason = Main.getDebugHandler().getBreakReason(); - List callStackFiles = new ArrayList<>(); - List callStackLines = new ArrayList<>(); - - callStackFiles.add(Main.getDebugHandler().moduleToString(info.file)); - callStackLines.add(info.line); - - for (int i = 0; i < info.files.size(); i++) { - callStackFiles.add(Main.getDebugHandler().moduleToString(info.files.get(i))); - callStackLines.add(info.lines.get(i)); - } - Object[][] data = new Object[callStackFiles.size()][2]; - for (int i = 0; i < callStackFiles.size(); i++) { - data[i][0] = callStackFiles.get(i); - data[i][1] = callStackLines.get(i); - } - - DefaultTableModel tm = new DefaultTableModel(data, new Object[]{ - AppStrings.translate("callStack.header.file"), - AppStrings.translate("callStack.header.line") - }) { - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - - }; - callStackTable.setModel(tm); - - Object[][] data2 = new Object[info.stacks.size()][1]; - for (int i = 0; i < info.stacks.size(); i++) { - data2[i][0] = info.stacks.get(i); - } - stackTable.setModel(new DefaultTableModel(data2, new Object[]{AppStrings.translate("stack.header.item")}) { - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - - }); - } else { - callStackTable.setModel(new DefaultTableModel()); - stackTable.setModel(new DefaultTableModel()); - } - InConstantPool cpool = Main.getDebugHandler().getConstantPool(); - if (cpool != null) { - Object[][] data2 = new Object[cpool.vars.size()][2]; - for (int i = 0; i < cpool.vars.size(); i++) { - data2[i][0] = cpool.ids.get(i); - data2[i][1] = cpool.vars.get(i).value; - } - constantPoolTable.setModel(new DefaultTableModel(data2, new Object[]{ - AppStrings.translate("constantpool.header.id"), - AppStrings.translate("constantpool.header.value") - }) { - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - - }); - } - - varTabs.removeAll(); - tabTypes.clear(); - JPanel pa; - if (debugRegistersTable.getRowCount() > 0) { - tabTypes.add(SelectedTab.REGISTERS); - pa = new JPanel(new BorderLayout()); - pa.add(new JScrollPane(debugRegistersTable), BorderLayout.CENTER); - varTabs.addTab(AppStrings.translate("variables.header.registers"), pa); - } - if (debugLocalsTable.getRowCount() > 0) { - tabTypes.add(SelectedTab.LOCALS); - - pa = new JPanel(new BorderLayout()); - pa.add(new JScrollPane(debugLocalsTable), BorderLayout.CENTER); - varTabs.addTab(AppStrings.translate("variables.header.locals"), pa); - } - - if (debugScopeTable.getRowCount() > 0) { - tabTypes.add(SelectedTab.SCOPECHAIN); - - pa = new JPanel(new BorderLayout()); - pa.add(new JScrollPane(debugScopeTable), BorderLayout.CENTER); - varTabs.addTab(AppStrings.translate("variables.header.scopeChain"), pa); - } - - if (constantPoolTable.getRowCount() > 0) { - tabTypes.add(SelectedTab.CONSTANTPOOL); - - pa = new JPanel(new BorderLayout()); - pa.add(new JScrollPane(constantPoolTable), BorderLayout.CENTER); - varTabs.addTab(AppStrings.translate("constantpool.header"), pa); - } - - if (callStackTable.getRowCount() > 0) { - tabTypes.add(SelectedTab.CALLSTACK); - - pa = new JPanel(new BorderLayout()); - pa.add(new JScrollPane(callStackTable), BorderLayout.CENTER); - varTabs.addTab(AppStrings.translate("callStack.header"), pa); - } - if (stackTable.getRowCount() > 0) { - tabTypes.add(SelectedTab.STACK); - - pa = new JPanel(new BorderLayout()); - pa.add(new JScrollPane(stackTable), BorderLayout.CENTER); - varTabs.addTab(AppStrings.translate("stack.header"), pa); - } - if (logLength > 0) { - tabTypes.add(SelectedTab.LOG); - - pa = new JPanel(new BorderLayout()); - pa.add(new JScrollPane(traceLogTextarea), BorderLayout.CENTER); - JButton clearButton = new JButton(AppStrings.translate("debuglog.button.clear")); - clearButton.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - traceLogTextarea.setText(""); - logLength = 0; - refresh(); - } - }); - JPanel butPanel = new JPanel(new FlowLayout()); - butPanel.add(clearButton); - pa.add(butPanel, BorderLayout.SOUTH); - varTabs.addTab(AppStrings.translate("debuglog.header"), pa); - } - boolean newVisible = !tabTypes.isEmpty(); - if (newVisible != isVisible()) { - setVisible(newVisible); - } - if (!tabTypes.isEmpty()) { - if (oldSel != null && !tabTypes.contains(oldSel)) { - oldSel = null; - } - } - if (oldSel != null) { - selectedTab = oldSel; - varTabs.setSelectedIndex(tabTypes.indexOf(selectedTab)); - } - setLoading(false); - } - - } - }); - - } - - public void dispose() { - Main.getDebugHandler().removeBreakListener(listener); - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.debugger.flash.Variable; +import com.jpexs.debugger.flash.messages.in.InBreakAtExt; +import com.jpexs.debugger.flash.messages.in.InConstantPool; +import com.jpexs.debugger.flash.messages.in.InFrame; +import com.jpexs.decompiler.flash.gui.DebuggerHandler.BreakListener; +import com.jpexs.decompiler.flash.gui.abc.ABCPanel; +import de.hameister.treetable.MyTreeTable; +import de.hameister.treetable.MyTreeTableModel; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.table.DefaultTableModel; +import javax.swing.tree.TreePath; + +/** + * + * @author JPEXS + */ +public class DebugPanel extends JPanel { + + private MyTreeTable debugRegistersTable; + + private MyTreeTable debugLocalsTable; //JTable debugLocalsTable; + + private MyTreeTable debugScopeTable; + + private JTable callStackTable; + + private JTable stackTable; + + private JTable constantPoolTable; + + private JTabbedPane varTabs; + + private BreakListener listener; + + private JTextArea traceLogTextarea; + + private int logLength = 0; + + private List tabTypes = new ArrayList<>(); + + private boolean loading = false; + + public static enum SelectedTab { + + LOG, STACK, SCOPECHAIN, LOCALS, REGISTERS, CALLSTACK, CONSTANTPOOL + } + + public synchronized boolean isLoading() { + return loading; + } + + public synchronized void setLoading(boolean loading) { + this.loading = loading; + } + + private SelectedTab selectedTab = null; + + private void safeSetTreeModel(MyTreeTable tt, MyTreeTableModel tmodel) { + List> expanded = View.getExpandedNodes(tt.getTree()); + + int[] selRows = tt.getSelectedRows(); + + TreePath[] selPaths = new TreePath[selRows.length]; + for (int i = 0; i < selRows.length; i++) { + selPaths[i] = tt.getTree().getPathForRow(selRows[i]); + } + tt.setTreeModel(tmodel); + //tt.getTree().setRootVisible(false); + + View.expandTreeNodes(tt.getTree(), expanded); + for (int i = 0; i < selRows.length; i++) { + selRows[i] = tt.getTree().getRowForPath(selPaths[i]); + if (i == 0) { + tt.setRowSelectionInterval(selRows[i], selRows[i]); + } else { + tt.addRowSelectionInterval(selRows[i], selRows[i]); + } + } + + } + + public DebugPanel() { + super(new BorderLayout()); + debugRegistersTable = new MyTreeTable(new ABCPanel.VariablesTableModel(debugRegistersTable, new ArrayList<>(), new ArrayList<>()), false); + debugLocalsTable = new MyTreeTable(new ABCPanel.VariablesTableModel(debugLocalsTable, new ArrayList<>(), new ArrayList<>()), false); + + //Add watch feature, commented out. I tried it, but without success. I can't add watch in Flash Pro or FDB either. :-( + /* + //locales + debug.watch.add = Add watch to %name% + debug.watch.add.read = Read + debug.watch.add.write = Write + debug.watch.add.readwrite = Read+Write + + error.debug.watch.add = Cannot add watch to this variable. + + + MouseAdapter watchHandler = new MouseAdapter() { + + @Override + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + dopop(e); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + dopop(e); + } + } + + private void dopop(MouseEvent e) { + if (debugLocalsTable.getSelectedRow() == -1) { + return; + } + Variable v = ((ABCPanel.VariablesTableModel) debugLocalsTable.getModel()).getVars().get(debugLocalsTable.getSelectedRow()); + final long v_id = ((ABCPanel.VariablesTableModel) debugLocalsTable.getModel()).getVarIds().get(debugLocalsTable.getSelectedRow()); + + JPopupMenu pm = new JPopupMenu(); + JMenu addWatchMenu = new JMenu(AppStrings.translate("debug.watch.add").replace("%name%", v.name)); + JMenuItem watchReadMenuItem = new JMenuItem(AppStrings.translate("debug.watch.add.read")); + watchReadMenuItem.addActionListener((ActionEvent e1) -> { + if (!Main.addWatch(v, v_id, true, false)) { + View.showMessageDialog(DebugPanel.this, AppStrings.translate("debug.watch.add"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + }); + JMenuItem watchWriteMenuItem = new JMenuItem(AppStrings.translate("debug.watch.add.write")); + watchWriteMenuItem.addActionListener((ActionEvent e1) -> { + if (!Main.addWatch(v, v_id, false, true)) { + View.showMessageDialog(DebugPanel.this, AppStrings.translate("debug.watch.add"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + }); + JMenuItem watchReadWriteMenuItem = new JMenuItem(AppStrings.translate("debug.watch.add.readwrite")); + watchReadWriteMenuItem.addActionListener((ActionEvent e1) -> { + if (!Main.addWatch(v, v_id, true, true)) { + View.showMessageDialog(DebugPanel.this, AppStrings.translate("debug.watch.add"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + }); + + addWatchMenu.add(watchReadMenuItem); + addWatchMenu.add(watchWriteMenuItem); + addWatchMenu.add(watchReadWriteMenuItem); + pm.add(addWatchMenu); + + pm.show(e.getComponent(), e.getX(), e.getY()); + } + }; + + debugLocalsTable.addMouseListener(watchHandler); + debugScopeTable.addMouseListener(watchHandler); + + */ + debugScopeTable = new MyTreeTable(new ABCPanel.VariablesTableModel(debugScopeTable, new ArrayList<>(), new ArrayList<>()), false); + + callStackTable = new JTable(); + stackTable = new JTable(); + constantPoolTable = new JTable(); + traceLogTextarea = new JTextArea(); + traceLogTextarea.setEditable(false); + traceLogTextarea.setOpaque(false); + traceLogTextarea.setFont(new JLabel().getFont()); + traceLogTextarea.setBackground(Color.white); + + Main.getDebugHandler().addTraceListener(new DebuggerHandler.TraceListener() { + + @Override + public void trace(String... val) { + for (String s : val) { + String add = "trace: " + s + "\r\n"; + boolean wasEmpty = logLength == 0; + logLength += add.length(); + traceLogTextarea.append(add); + try { + traceLogTextarea.setCaretPosition(logLength); + } catch (IllegalArgumentException iex) { + //ignore + } + if (wasEmpty) { + refresh(); + } + } + } + }); + + Main.getDebugHandler().addConnectionListener(new DebuggerHandler.ConnectionListener() { + + @Override + public void connected() { + } + + @Override + public void disconnected() { + refresh(); + } + }); + + Main.getDebugHandler().addBreakListener(listener = new DebuggerHandler.BreakListener() { + + @Override + public void doContinue() { + View.execInEventDispatch(new Runnable() { + + @Override + public void run() { + refresh(); + } + + }); + } + + @Override + public void breakAt(String scriptName, int line, int classIndex, int traitIndex, int methodIndex) { + View.execInEventDispatch(new Runnable() { + + @Override + public void run() { + refresh(); + } + }); + + } + }); + + varTabs = new JTabbedPane(); + varTabs.addChangeListener(new ChangeListener() { + + @Override + public void stateChanged(ChangeEvent e) { + if (e.getSource() == varTabs) { + if (isLoading()) { + return; + } + synchronized (DebugPanel.this) { + int si = varTabs.getSelectedIndex(); + if (si > -1 && si < tabTypes.size()) { + selectedTab = tabTypes.get(si); + } + } + } + } + }); + + add(new HeaderLabel(AppStrings.translate("debugpanel.header")), BorderLayout.NORTH); + add(varTabs, BorderLayout.CENTER); + } + + public void refresh() { + + View.execInEventDispatch(new Runnable() { + + @Override + public void run() { + setLoading(true); + synchronized (DebugPanel.this) { + + SelectedTab oldSel = selectedTab; + InFrame f = Main.getDebugHandler().getFrame(); + if (f != null) { + + List regVarIds = new ArrayList<>(); + for (int i = 0; i < f.registers.size(); i++) { + regVarIds.add(0L); + } + safeSetTreeModel(debugRegistersTable, new ABCPanel.VariablesTableModel(debugRegistersTable, f.registers, regVarIds)); + List locals = new ArrayList<>(); + locals.addAll(f.arguments); + locals.addAll(f.variables); + + List localIds = new ArrayList<>(); + localIds.addAll(f.argumentFrameIds); + localIds.addAll(f.frameIds); + + safeSetTreeModel(debugLocalsTable, new ABCPanel.VariablesTableModel(debugLocalsTable, locals, localIds)); + safeSetTreeModel(debugScopeTable, new ABCPanel.VariablesTableModel(debugScopeTable, f.scopeChain, f.scopeChainFrameIds)); + + /*TableModelListener refreshListener = new TableModelListener() { + @Override + public void tableChanged(TableModelEvent e) { + Main.getDebugHandler().refreshFrame(); + refresh(); + } + };*/ + TreeModelListener refreshListener = new TreeModelListener() { + @Override + public void treeNodesChanged(TreeModelEvent e) { + Main.getDebugHandler().refreshFrame(); + refresh(); + } + + @Override + public void treeNodesInserted(TreeModelEvent e) { + Main.getDebugHandler().refreshFrame(); + refresh(); + } + + @Override + public void treeNodesRemoved(TreeModelEvent e) { + Main.getDebugHandler().refreshFrame(); + refresh(); + } + + @Override + public void treeStructureChanged(TreeModelEvent e) { + Main.getDebugHandler().refreshFrame(); + refresh(); + } + }; + debugLocalsTable.getTreeTableModel().addTreeModelListener(refreshListener); + debugScopeTable.getTreeTableModel().addTreeModelListener(refreshListener); + } else { + debugRegistersTable.setTreeModel(new ABCPanel.VariablesTableModel(debugRegistersTable, new ArrayList<>(), new ArrayList<>())); + debugLocalsTable.setTreeModel(new ABCPanel.VariablesTableModel(debugLocalsTable, new ArrayList<>(), new ArrayList<>())); + debugScopeTable.setTreeModel(new ABCPanel.VariablesTableModel(debugScopeTable, new ArrayList<>(), new ArrayList<>())); + } + InBreakAtExt info = Main.getDebugHandler().getBreakInfo(); + if (info != null) { + //InBreakReason reason = Main.getDebugHandler().getBreakReason(); + List callStackFiles = new ArrayList<>(); + List callStackLines = new ArrayList<>(); + + callStackFiles.add(Main.getDebugHandler().moduleToString(info.file)); + callStackLines.add(info.line); + + for (int i = 0; i < info.files.size(); i++) { + callStackFiles.add(Main.getDebugHandler().moduleToString(info.files.get(i))); + callStackLines.add(info.lines.get(i)); + } + Object[][] data = new Object[callStackFiles.size()][2]; + for (int i = 0; i < callStackFiles.size(); i++) { + data[i][0] = callStackFiles.get(i); + data[i][1] = callStackLines.get(i); + } + + DefaultTableModel tm = new DefaultTableModel(data, new Object[]{ + AppStrings.translate("callStack.header.file"), + AppStrings.translate("callStack.header.line") + }) { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + + }; + callStackTable.setModel(tm); + + Object[][] data2 = new Object[info.stacks.size()][1]; + for (int i = 0; i < info.stacks.size(); i++) { + data2[i][0] = info.stacks.get(i); + } + stackTable.setModel(new DefaultTableModel(data2, new Object[]{AppStrings.translate("stack.header.item")}) { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + + }); + } else { + callStackTable.setModel(new DefaultTableModel()); + stackTable.setModel(new DefaultTableModel()); + } + InConstantPool cpool = Main.getDebugHandler().getConstantPool(); + if (cpool != null) { + Object[][] data2 = new Object[cpool.vars.size()][2]; + for (int i = 0; i < cpool.vars.size(); i++) { + data2[i][0] = cpool.ids.get(i); + data2[i][1] = cpool.vars.get(i).value; + } + constantPoolTable.setModel(new DefaultTableModel(data2, new Object[]{ + AppStrings.translate("constantpool.header.id"), + AppStrings.translate("constantpool.header.value") + }) { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + + }); + } + + varTabs.removeAll(); + tabTypes.clear(); + JPanel pa; + if (debugRegistersTable.getRowCount() > 0) { + tabTypes.add(SelectedTab.REGISTERS); + pa = new JPanel(new BorderLayout()); + pa.add(new JScrollPane(debugRegistersTable), BorderLayout.CENTER); + varTabs.addTab(AppStrings.translate("variables.header.registers"), pa); + } + if (debugLocalsTable.getRowCount() > 0) { + tabTypes.add(SelectedTab.LOCALS); + + pa = new JPanel(new BorderLayout()); + pa.add(new JScrollPane(debugLocalsTable), BorderLayout.CENTER); + varTabs.addTab(AppStrings.translate("variables.header.locals"), pa); + } + + if (debugScopeTable.getRowCount() > 0) { + tabTypes.add(SelectedTab.SCOPECHAIN); + + pa = new JPanel(new BorderLayout()); + pa.add(new JScrollPane(debugScopeTable), BorderLayout.CENTER); + varTabs.addTab(AppStrings.translate("variables.header.scopeChain"), pa); + } + + if (constantPoolTable.getRowCount() > 0) { + tabTypes.add(SelectedTab.CONSTANTPOOL); + + pa = new JPanel(new BorderLayout()); + pa.add(new JScrollPane(constantPoolTable), BorderLayout.CENTER); + varTabs.addTab(AppStrings.translate("constantpool.header"), pa); + } + + if (callStackTable.getRowCount() > 0) { + tabTypes.add(SelectedTab.CALLSTACK); + + pa = new JPanel(new BorderLayout()); + pa.add(new JScrollPane(callStackTable), BorderLayout.CENTER); + varTabs.addTab(AppStrings.translate("callStack.header"), pa); + } + if (stackTable.getRowCount() > 0) { + tabTypes.add(SelectedTab.STACK); + + pa = new JPanel(new BorderLayout()); + pa.add(new JScrollPane(stackTable), BorderLayout.CENTER); + varTabs.addTab(AppStrings.translate("stack.header"), pa); + } + if (logLength > 0) { + tabTypes.add(SelectedTab.LOG); + + pa = new JPanel(new BorderLayout()); + pa.add(new JScrollPane(traceLogTextarea), BorderLayout.CENTER); + JButton clearButton = new JButton(AppStrings.translate("debuglog.button.clear")); + clearButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + traceLogTextarea.setText(""); + logLength = 0; + refresh(); + } + }); + JPanel butPanel = new JPanel(new FlowLayout()); + butPanel.add(clearButton); + pa.add(butPanel, BorderLayout.SOUTH); + varTabs.addTab(AppStrings.translate("debuglog.header"), pa); + } + boolean newVisible = !tabTypes.isEmpty(); + if (newVisible != isVisible()) { + setVisible(newVisible); + } + if (!tabTypes.isEmpty()) { + if (oldSel != null && !tabTypes.contains(oldSel)) { + oldSel = null; + } + } + if (oldSel != null) { + selectedTab = oldSel; + varTabs.setSelectedIndex(tabTypes.indexOf(selectedTab)); + } + setLoading(false); + } + + } + }); + + } + + public void dispose() { + Main.getDebugHandler().removeBreakListener(listener); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/ExportDialog.java b/src/com/jpexs/decompiler/flash/gui/ExportDialog.java index b09134c99..fea512776 100644 --- a/src/com/jpexs/decompiler/flash/gui/ExportDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/ExportDialog.java @@ -1,366 +1,366 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui; - -import com.jpexs.decompiler.flash.abc.ScriptPack; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.exporters.modes.BinaryDataExportMode; -import com.jpexs.decompiler.flash.exporters.modes.ButtonExportMode; -import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; -import com.jpexs.decompiler.flash.exporters.modes.FrameExportMode; -import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode; -import com.jpexs.decompiler.flash.exporters.modes.MorphShapeExportMode; -import com.jpexs.decompiler.flash.exporters.modes.MovieExportMode; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; -import com.jpexs.decompiler.flash.exporters.modes.SoundExportMode; -import com.jpexs.decompiler.flash.exporters.modes.SpriteExportMode; -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.DefineVideoStreamTag; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.flash.tags.base.ButtonTag; -import com.jpexs.decompiler.flash.tags.base.FontTag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; -import com.jpexs.decompiler.flash.tags.base.ShapeTag; -import com.jpexs.decompiler.flash.tags.base.SoundTag; -import com.jpexs.decompiler.flash.tags.base.SymbolClassTypeTag; -import com.jpexs.decompiler.flash.tags.base.TextTag; -import com.jpexs.decompiler.flash.timeline.Frame; -import com.jpexs.decompiler.flash.treeitems.TreeItem; -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.event.ActionEvent; -import java.util.Arrays; -import java.util.List; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextField; - -/** - * - * @author JPEXS - */ -public class ExportDialog extends AppDialog { - - private int result = ERROR_OPTION; - - String[] optionNames = { - TagTreeModel.FOLDER_SHAPES, - TagTreeModel.FOLDER_TEXTS, - TagTreeModel.FOLDER_IMAGES, - TagTreeModel.FOLDER_MOVIES, - TagTreeModel.FOLDER_SOUNDS, - TagTreeModel.FOLDER_SCRIPTS, - TagTreeModel.FOLDER_BINARY_DATA, - TagTreeModel.FOLDER_FRAMES, - TagTreeModel.FOLDER_SPRITES, - TagTreeModel.FOLDER_BUTTONS, - TagTreeModel.FOLDER_FONTS, - TagTreeModel.FOLDER_MORPHSHAPES, - "symbolclass" - }; - - //Display options only when these classes found - Class[][] objClasses = { - {ShapeTag.class}, - {TextTag.class}, - {ImageTag.class}, - {DefineVideoStreamTag.class}, - {SoundTag.class}, - {ASMSource.class, ScriptPack.class}, - {DefineBinaryDataTag.class}, - {Frame.class}, - {Frame.class}, - {ButtonTag.class}, - {FontTag.class}, - {MorphShapeTag.class}, - {SymbolClassTypeTag.class} - }; - - //Enum classes for values - Class[] optionClasses = { - ShapeExportMode.class, - TextExportMode.class, - ImageExportMode.class, - MovieExportMode.class, - SoundExportMode.class, - ScriptExportMode.class, - BinaryDataExportMode.class, - FrameExportMode.class, - SpriteExportMode.class, - ButtonExportMode.class, - FontExportMode.class, - MorphShapeExportMode.class, - SymbolClassExportMode.class - }; - - Class[] zoomClasses = { - ShapeExportMode.class, - TextExportMode.class, - FrameExportMode.class, - MorphShapeExportMode.class - }; - - private final JComboBox[] combos; - - private final JCheckBox[] checkBoxes; - - private final JCheckBox selectAllCheckBox; - - private JTextField zoomTextField = new JTextField(); - - public E getValue(Class option) { - for (int i = 0; i < optionClasses.length; i++) { - if (option == optionClasses[i]) { - E values[] = option.getEnumConstants(); - return values[combos[i].getSelectedIndex()]; - } - } - - return null; - } - - public boolean isOptionEnabled(Class option) { - for (int i = 0; i < optionClasses.length; i++) { - if (option == optionClasses[i]) { - return checkBoxes[i].isSelected(); - } - } - - return false; - } - - public double getZoom() { - return Double.parseDouble(zoomTextField.getText()) / 100; - } - - private void saveConfig() { - StringBuilder cfg = new StringBuilder(); - for (int i = 0; i < optionNames.length; i++) { - int selIndex = combos[i].getSelectedIndex(); - Class c = optionClasses[i]; - Object vals[] = c.getEnumConstants(); - String key = optionNames[i] + "." + vals[selIndex].toString().toLowerCase(); - if (i > 0) { - cfg.append(","); - } - cfg.append(key); - } - - Configuration.lastSelectedExportZoom.set(Double.parseDouble(zoomTextField.getText()) / 100); - Configuration.lastSelectedExportFormats.set(cfg.toString()); - } - - public ExportDialog(List exportables) { - setTitle(translate("dialog.title")); - setResizable(false); - - Container cnt = getContentPane(); - cnt.setLayout(new BorderLayout()); - JPanel comboPanel = new JPanel(null); - int labWidth = 0; - boolean[] exportableExistsArray = new boolean[optionNames.length]; - for (int i = 0; i < optionNames.length; i++) { - boolean exportableExists = false; - if (exportables == null) { - 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 (!exportableExists) { - continue; - } - - exportableExistsArray[i] = true; - - JLabel label = new JLabel(translate(optionNames[i])); - if (label.getPreferredSize().width > labWidth) { - labWidth = label.getPreferredSize().width; - } - } - - String exportFormatsStr = Configuration.lastSelectedExportFormats.get(); - if ("".equals(exportFormatsStr)) { - exportFormatsStr = null; - } - - String exportFormatsArr[] = new String[0]; - if (exportFormatsStr != null) { - if (exportFormatsStr.contains(",")) { - exportFormatsArr = exportFormatsStr.split(","); - } else { - exportFormatsArr = new String[]{exportFormatsStr}; - } - - } - - int comboWidth = 200; - int checkBoxWidth; - int top = 10; - - List exportFormats = Arrays.asList(exportFormatsArr); - combos = new JComboBox[optionNames.length]; - checkBoxes = new JCheckBox[optionNames.length]; - selectAllCheckBox = new JCheckBox(); - checkBoxWidth = selectAllCheckBox.getPreferredSize().width; - selectAllCheckBox.setBounds(10 + labWidth + 10 + comboWidth + 10, top, checkBoxWidth, selectAllCheckBox.getPreferredSize().height); - selectAllCheckBox.setSelected(true); - selectAllCheckBox.addActionListener((ActionEvent e) -> { - boolean selected = selectAllCheckBox.isSelected(); - for (JCheckBox checkBox : checkBoxes) { - if (checkBox != null) { - checkBox.setSelected(selected); - } - } - }); - comboPanel.add(selectAllCheckBox); - top += selectAllCheckBox.getHeight(); - - 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]; - int itemIndex = -1; - for (int j = 0; j < vals.length; j++) { - - String key = optionNames[i] + "." + vals[j].toString().toLowerCase(); - if (exportFormats.contains(key)) { - itemIndex = j; - } - names[j] = translate(key); - } - - combos[i] = new JComboBox<>(names); - if (itemIndex > -1) { - combos[i].setSelectedIndex(itemIndex); - } - - combos[i].setBounds(10 + labWidth + 10, top, comboWidth, combos[i].getPreferredSize().height); - - checkBoxes[i] = new JCheckBox(); - checkBoxes[i].setBounds(10 + labWidth + 10 + comboWidth + 10, top, checkBoxWidth, checkBoxes[i].getPreferredSize().height); - checkBoxes[i].setSelected(true); - - if (!exportableExistsArray[i]) { - continue; - } - - if (Arrays.asList(zoomClasses).contains(c)) { - zoomable = true; - } - - JLabel lab = new JLabel(translate(optionNames[i])); - lab.setBounds(10, top, lab.getPreferredSize().width, lab.getPreferredSize().height); - comboPanel.add(lab); - comboPanel.add(checkBoxes[i]); - comboPanel.add(combos[i]); - top += combos[i].getHeight(); - } - - int zoomWidth = 50; - if (zoomable) { - top += 2; - JLabel zlab = new JLabel(translate("zoom")); - zlab.setBounds(10, top + 4, zlab.getPreferredSize().width, zlab.getPreferredSize().height); - zoomTextField.setBounds(10 + labWidth + 10, top, zoomWidth, zoomTextField.getPreferredSize().height); - JLabel pctLabel = new JLabel(translate("zoom.percent")); - pctLabel.setBounds(10 + labWidth + 10 + zoomWidth + 5, top + 4, 20, pctLabel.getPreferredSize().height); - - comboPanel.add(zlab); - comboPanel.add(zoomTextField); - comboPanel.add(pctLabel); - top += zoomTextField.getHeight(); - } - - Dimension dim = new Dimension(10 + labWidth + 10 + checkBoxWidth + 10 + comboWidth + 10, top + 10); - comboPanel.setMinimumSize(dim); - comboPanel.setPreferredSize(dim); - cnt.add(comboPanel, BorderLayout.CENTER); - - JPanel buttonsPanel = new JPanel(new FlowLayout()); - JButton okButton = new JButton(translate("button.ok")); - okButton.addActionListener(this::okButtonActionPerformed); - - JButton cancelButton = new JButton(translate("button.cancel")); - cancelButton.addActionListener(this::cancelButtonActionPerformed); - - buttonsPanel.add(okButton); - buttonsPanel.add(cancelButton); - - cnt.add(buttonsPanel, BorderLayout.SOUTH); - pack(); - View.centerScreen(this); - View.setWindowIcon(this); - getRootPane().setDefaultButton(okButton); - setModal(true); - String pct = "" + Configuration.lastSelectedExportZoom.get() * 100; - if (pct.endsWith(".0")) { - pct = pct.substring(0, pct.length() - 2); - } - - zoomTextField.setText(pct); - } - - @Override - public void setVisible(boolean b) { - if (b) { - result = ERROR_OPTION; - } - super.setVisible(b); - } - - private void okButtonActionPerformed(ActionEvent evt) { - result = OK_OPTION; - try { - saveConfig(); - } catch (NumberFormatException nfe) { - JOptionPane.showMessageDialog(ExportDialog.this, translate("zoom.invalid"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - zoomTextField.requestFocusInWindow(); - return; - } - - setVisible(false); - } - - private void cancelButtonActionPerformed(ActionEvent evt) { - result = CANCEL_OPTION; - setVisible(false); - } - - public int showExportDialog() { - setVisible(true); - return result; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.abc.ScriptPack; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.exporters.modes.BinaryDataExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ButtonExportMode; +import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; +import com.jpexs.decompiler.flash.exporters.modes.FrameExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode; +import com.jpexs.decompiler.flash.exporters.modes.MorphShapeExportMode; +import com.jpexs.decompiler.flash.exporters.modes.MovieExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; +import com.jpexs.decompiler.flash.exporters.modes.SoundExportMode; +import com.jpexs.decompiler.flash.exporters.modes.SpriteExportMode; +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.DefineVideoStreamTag; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; +import com.jpexs.decompiler.flash.tags.base.SymbolClassTypeTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.timeline.Frame; +import com.jpexs.decompiler.flash.treeitems.TreeItem; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.util.Arrays; +import java.util.List; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +/** + * + * @author JPEXS + */ +public class ExportDialog extends AppDialog { + + private int result = ERROR_OPTION; + + String[] optionNames = { + TagTreeModel.FOLDER_SHAPES, + TagTreeModel.FOLDER_TEXTS, + TagTreeModel.FOLDER_IMAGES, + TagTreeModel.FOLDER_MOVIES, + TagTreeModel.FOLDER_SOUNDS, + TagTreeModel.FOLDER_SCRIPTS, + TagTreeModel.FOLDER_BINARY_DATA, + TagTreeModel.FOLDER_FRAMES, + TagTreeModel.FOLDER_SPRITES, + TagTreeModel.FOLDER_BUTTONS, + TagTreeModel.FOLDER_FONTS, + TagTreeModel.FOLDER_MORPHSHAPES, + "symbolclass" + }; + + //Display options only when these classes found + Class[][] objClasses = { + {ShapeTag.class}, + {TextTag.class}, + {ImageTag.class}, + {DefineVideoStreamTag.class}, + {SoundTag.class}, + {ASMSource.class, ScriptPack.class}, + {DefineBinaryDataTag.class}, + {Frame.class}, + {Frame.class}, + {ButtonTag.class}, + {FontTag.class}, + {MorphShapeTag.class}, + {SymbolClassTypeTag.class} + }; + + //Enum classes for values + Class[] optionClasses = { + ShapeExportMode.class, + TextExportMode.class, + ImageExportMode.class, + MovieExportMode.class, + SoundExportMode.class, + ScriptExportMode.class, + BinaryDataExportMode.class, + FrameExportMode.class, + SpriteExportMode.class, + ButtonExportMode.class, + FontExportMode.class, + MorphShapeExportMode.class, + SymbolClassExportMode.class + }; + + Class[] zoomClasses = { + ShapeExportMode.class, + TextExportMode.class, + FrameExportMode.class, + MorphShapeExportMode.class + }; + + private final JComboBox[] combos; + + private final JCheckBox[] checkBoxes; + + private final JCheckBox selectAllCheckBox; + + private JTextField zoomTextField = new JTextField(); + + public E getValue(Class option) { + for (int i = 0; i < optionClasses.length; i++) { + if (option == optionClasses[i]) { + E[] values = option.getEnumConstants(); + return values[combos[i].getSelectedIndex()]; + } + } + + return null; + } + + public boolean isOptionEnabled(Class option) { + for (int i = 0; i < optionClasses.length; i++) { + if (option == optionClasses[i]) { + return checkBoxes[i].isSelected(); + } + } + + return false; + } + + public double getZoom() { + return Double.parseDouble(zoomTextField.getText()) / 100; + } + + private void saveConfig() { + StringBuilder cfg = new StringBuilder(); + for (int i = 0; i < optionNames.length; i++) { + int selIndex = combos[i].getSelectedIndex(); + Class c = optionClasses[i]; + Object[] vals = c.getEnumConstants(); + String key = optionNames[i] + "." + vals[selIndex].toString().toLowerCase(); + if (i > 0) { + cfg.append(","); + } + cfg.append(key); + } + + Configuration.lastSelectedExportZoom.set(Double.parseDouble(zoomTextField.getText()) / 100); + Configuration.lastSelectedExportFormats.set(cfg.toString()); + } + + public ExportDialog(List exportables) { + setTitle(translate("dialog.title")); + setResizable(false); + + Container cnt = getContentPane(); + cnt.setLayout(new BorderLayout()); + JPanel comboPanel = new JPanel(null); + int labWidth = 0; + boolean[] exportableExistsArray = new boolean[optionNames.length]; + for (int i = 0; i < optionNames.length; i++) { + boolean exportableExists = false; + if (exportables == null) { + 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 (!exportableExists) { + continue; + } + + exportableExistsArray[i] = true; + + JLabel label = new JLabel(translate(optionNames[i])); + if (label.getPreferredSize().width > labWidth) { + labWidth = label.getPreferredSize().width; + } + } + + String exportFormatsStr = Configuration.lastSelectedExportFormats.get(); + if ("".equals(exportFormatsStr)) { + exportFormatsStr = null; + } + + String[] exportFormatsArr = new String[0]; + if (exportFormatsStr != null) { + if (exportFormatsStr.contains(",")) { + exportFormatsArr = exportFormatsStr.split(","); + } else { + exportFormatsArr = new String[]{exportFormatsStr}; + } + + } + + int comboWidth = 200; + int checkBoxWidth; + int top = 10; + + List exportFormats = Arrays.asList(exportFormatsArr); + combos = new JComboBox[optionNames.length]; + checkBoxes = new JCheckBox[optionNames.length]; + selectAllCheckBox = new JCheckBox(); + checkBoxWidth = selectAllCheckBox.getPreferredSize().width; + selectAllCheckBox.setBounds(10 + labWidth + 10 + comboWidth + 10, top, checkBoxWidth, selectAllCheckBox.getPreferredSize().height); + selectAllCheckBox.setSelected(true); + selectAllCheckBox.addActionListener((ActionEvent e) -> { + boolean selected = selectAllCheckBox.isSelected(); + for (JCheckBox checkBox : checkBoxes) { + if (checkBox != null) { + checkBox.setSelected(selected); + } + } + }); + comboPanel.add(selectAllCheckBox); + top += selectAllCheckBox.getHeight(); + + 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]; + int itemIndex = -1; + for (int j = 0; j < vals.length; j++) { + + String key = optionNames[i] + "." + vals[j].toString().toLowerCase(); + if (exportFormats.contains(key)) { + itemIndex = j; + } + names[j] = translate(key); + } + + combos[i] = new JComboBox<>(names); + if (itemIndex > -1) { + combos[i].setSelectedIndex(itemIndex); + } + + combos[i].setBounds(10 + labWidth + 10, top, comboWidth, combos[i].getPreferredSize().height); + + checkBoxes[i] = new JCheckBox(); + checkBoxes[i].setBounds(10 + labWidth + 10 + comboWidth + 10, top, checkBoxWidth, checkBoxes[i].getPreferredSize().height); + checkBoxes[i].setSelected(true); + + if (!exportableExistsArray[i]) { + continue; + } + + if (Arrays.asList(zoomClasses).contains(c)) { + zoomable = true; + } + + JLabel lab = new JLabel(translate(optionNames[i])); + lab.setBounds(10, top, lab.getPreferredSize().width, lab.getPreferredSize().height); + comboPanel.add(lab); + comboPanel.add(checkBoxes[i]); + comboPanel.add(combos[i]); + top += combos[i].getHeight(); + } + + int zoomWidth = 50; + if (zoomable) { + top += 2; + JLabel zlab = new JLabel(translate("zoom")); + zlab.setBounds(10, top + 4, zlab.getPreferredSize().width, zlab.getPreferredSize().height); + zoomTextField.setBounds(10 + labWidth + 10, top, zoomWidth, zoomTextField.getPreferredSize().height); + JLabel pctLabel = new JLabel(translate("zoom.percent")); + pctLabel.setBounds(10 + labWidth + 10 + zoomWidth + 5, top + 4, 20, pctLabel.getPreferredSize().height); + + comboPanel.add(zlab); + comboPanel.add(zoomTextField); + comboPanel.add(pctLabel); + top += zoomTextField.getHeight(); + } + + Dimension dim = new Dimension(10 + labWidth + 10 + checkBoxWidth + 10 + comboWidth + 10, top + 10); + comboPanel.setMinimumSize(dim); + comboPanel.setPreferredSize(dim); + cnt.add(comboPanel, BorderLayout.CENTER); + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + JButton okButton = new JButton(translate("button.ok")); + okButton.addActionListener(this::okButtonActionPerformed); + + JButton cancelButton = new JButton(translate("button.cancel")); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + + buttonsPanel.add(okButton); + buttonsPanel.add(cancelButton); + + cnt.add(buttonsPanel, BorderLayout.SOUTH); + pack(); + View.centerScreen(this); + View.setWindowIcon(this); + getRootPane().setDefaultButton(okButton); + setModal(true); + String pct = "" + Configuration.lastSelectedExportZoom.get() * 100; + if (pct.endsWith(".0")) { + pct = pct.substring(0, pct.length() - 2); + } + + zoomTextField.setText(pct); + } + + @Override + public void setVisible(boolean b) { + if (b) { + result = ERROR_OPTION; + } + super.setVisible(b); + } + + private void okButtonActionPerformed(ActionEvent evt) { + result = OK_OPTION; + try { + saveConfig(); + } catch (NumberFormatException nfe) { + JOptionPane.showMessageDialog(ExportDialog.this, translate("zoom.invalid"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + zoomTextField.requestFocusInWindow(); + return; + } + + setVisible(false); + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + result = CANCEL_OPTION; + setVisible(false); + } + + public int showExportDialog() { + setVisible(true); + return result; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/FontEmbedDialog.java b/src/com/jpexs/decompiler/flash/gui/FontEmbedDialog.java index 42698e0f9..5d34915e8 100644 --- a/src/com/jpexs/decompiler/flash/gui/FontEmbedDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/FontEmbedDialog.java @@ -1,401 +1,401 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui; - -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.tags.font.CharacterRanges; -import com.jpexs.helpers.Helper; -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Font; -import java.awt.FontFormatException; -import java.awt.event.ActionEvent; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.io.File; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import java.util.TreeSet; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.JTextField; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.filechooser.FileFilter; - -/** - * - * @author JPEXS - */ -public class FontEmbedDialog extends AppDialog { - - private static final int SAMPLE_MAX_LENGTH = 50; - - private final JComboBox familyNamesSelection; - - private final JComboBox faceSelection; - - private final JCheckBox[] rangeCheckboxes; - - private final String rangeNames[]; - - private final JLabel[] rangeSamples; - - private final JTextField individualCharsField; - - private int result = ERROR_OPTION; - - private JLabel individialSample; - - private Font customFont; - - private final JCheckBox allCheckbox; - - public Font getSelectedFont() { - if (ttfFileRadio.isSelected() && customFont != null) { - return customFont; - } - return ((FontFace) faceSelection.getSelectedItem()).font; - } - - public Set getSelectedChars() { - Set chars = new TreeSet<>(); - Font f = getSelectedFont(); - if (allCheckbox.isSelected()) { - for (int i = 0; i < rangeCheckboxes.length; i++) { - int codes[] = CharacterRanges.rangeCodes(i); - for (int c : codes) { - if (f.canDisplay(c)) { - chars.add(c); - } - } - } - } else { - for (int i = 0; i < rangeCheckboxes.length; i++) { - if (rangeCheckboxes[i].isSelected()) { - int codes[] = CharacterRanges.rangeCodes(i); - for (int c : codes) { - if (f.canDisplay(c)) { - chars.add(c); - } - } - } - } - String indStr = individualCharsField.getText(); - for (int i = 0; i < indStr.length(); i++) { - if (f.canDisplay(indStr.codePointAt(i))) { - chars.add(indStr.codePointAt(i)); - } - } - } - return chars; - } - - private JRadioButton ttfFileRadio; - - private JRadioButton installedRadio; - - private void updateFaceSelection() { - faceSelection.setModel(FontPanel.getFaceModel((FontFamily) familyNamesSelection.getSelectedItem())); - } - - public FontEmbedDialog(FontFace selectedFace, String selectedChars) { - setSize(900, 600); - setDefaultCloseOperation(HIDE_ON_CLOSE); - setTitle(translate("dialog.title")); - - Container cnt = getContentPane(); - cnt.setLayout(new BoxLayout(cnt, BoxLayout.Y_AXIS)); - - JPanel selFontPanel = new JPanel(new FlowLayout()); - - installedRadio = new JRadioButton(translate("installed")); - ttfFileRadio = new JRadioButton(translate("ttffile.noselection")); - - ButtonGroup bg = new ButtonGroup(); - bg.add(installedRadio); - bg.add(ttfFileRadio); - - installedRadio.setSelected(true); - - individialSample = new JLabel(); - familyNamesSelection = new JComboBox<>(FontPanel.getFamilyModel()); - familyNamesSelection.setSelectedItem(new FontFamily(selectedFace.font)); - faceSelection = new JComboBox<>(); - updateFaceSelection(); - faceSelection.setSelectedItem(selectedFace); - JButton loadFromDiskButton = new JButton(View.getIcon("open16")); - loadFromDiskButton.setToolTipText(translate("button.loadfont")); - loadFromDiskButton.addActionListener(this::loadFromDiscButtonActionPerformed); - selFontPanel.add(installedRadio); - selFontPanel.add(familyNamesSelection); - selFontPanel.add(faceSelection); - selFontPanel.add(ttfFileRadio); - selFontPanel.add(loadFromDiskButton); - - installedRadio.addItemListener(new ItemListener() { - - @Override - public void itemStateChanged(ItemEvent e) { - if (e.getStateChange() == ItemEvent.SELECTED) { - updateCheckboxes(); - } - } - }); - - ttfFileRadio.addItemListener(new ItemListener() { - - @Override - public void itemStateChanged(ItemEvent e) { - if (e.getStateChange() == ItemEvent.SELECTED) { - if (ttfFileRadio.isSelected()) { - if (customFont == null) { - if (loadFromDisk()) { - updateCheckboxes(); - } else { - installedRadio.setSelected(true); - } - } else { - updateCheckboxes(); - } - } - } - } - }); - - cnt.add(selFontPanel); - JPanel rangesPanel = new JPanel(); - rangesPanel.setLayout(new BoxLayout(rangesPanel, BoxLayout.Y_AXIS)); - final int rc = CharacterRanges.rangeCount(); - rangeCheckboxes = new JCheckBox[rc]; - rangeSamples = new JLabel[rc]; - rangeNames = new String[rc]; - allCheckbox = new JCheckBox(translate("allcharacters")); - allCheckbox.addItemListener(new ItemListener() { - - @Override - public void itemStateChanged(ItemEvent e) { - if (e.getStateChange() == ItemEvent.SELECTED) { - for (int i = 0; i < rc; i++) { - rangeCheckboxes[i].setEnabled(false); - } - individualCharsField.setEnabled(false); - } else if (e.getStateChange() == ItemEvent.DESELECTED) { - for (int i = 0; i < rc; i++) { - rangeCheckboxes[i].setEnabled(true); - } - individualCharsField.setEnabled(true); - } - } - }); - JPanel rangeRowPanel = new JPanel(); - rangeRowPanel.setLayout(new BorderLayout()); - rangeRowPanel.add(allCheckbox, BorderLayout.WEST); - rangeRowPanel.setAlignmentX(0); - rangesPanel.add(rangeRowPanel); - - for (int i = 0; i < rc; i++) { - rangeNames[i] = CharacterRanges.rangeName(i); - rangeSamples[i] = new JLabel(""); - rangeCheckboxes[i] = new JCheckBox(rangeNames[i]); - rangeRowPanel = new JPanel(); - rangeRowPanel.setLayout(new BoxLayout(rangeRowPanel, BoxLayout.X_AXIS)); - rangeRowPanel.add(rangeCheckboxes[i]); - rangeRowPanel.add(Box.createHorizontalGlue()); - rangeRowPanel.add(rangeSamples[i]); - rangeRowPanel.setAlignmentX(0); - rangesPanel.add(rangeRowPanel); - } - cnt.add(new JScrollPane(rangesPanel)); - - JPanel specialPanel = new JPanel(); - specialPanel.setLayout(new BoxLayout(specialPanel, BoxLayout.X_AXIS)); - specialPanel.add(new JLabel(translate("label.individual"))); - individualCharsField = new JTextField(); - individualCharsField.setPreferredSize(new Dimension(100, individualCharsField.getPreferredSize().height)); - individialSample = new JLabel(); - specialPanel.add(individualCharsField); - - cnt.add(specialPanel); - cnt.add(individialSample); - - JPanel buttonsPanel = new JPanel(new FlowLayout()); - JButton okButton = new JButton(AppStrings.translate("button.ok")); - okButton.addActionListener(this::okButtonActionPerformed); - JButton cancelButton = new JButton(AppStrings.translate("button.cancel")); - cancelButton.addActionListener(this::cancelButtonActionPerformed); - buttonsPanel.add(okButton); - buttonsPanel.add(cancelButton); - cnt.add(buttonsPanel); - View.setWindowIcon(this); - View.centerScreen(this); - setModalityType(ModalityType.APPLICATION_MODAL); - individualCharsField.setText(selectedChars); - getRootPane().setDefaultButton(okButton); - familyNamesSelection.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - updateFaceSelection(); - updateCheckboxes(); - } - }); - faceSelection.addItemListener((ItemEvent e) -> { - updateCheckboxes(); - }); - updateCheckboxes(); - individualCharsField.getDocument().addDocumentListener(new DocumentListener() { - - @Override - public void insertUpdate(DocumentEvent e) { - updateIndividual(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - updateIndividual(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - updateIndividual(); - } - }); - } - - private void updateIndividual() { - String chars = individualCharsField.getText(); - Font f = getSelectedFont(); - StringBuilder visibleChars = new StringBuilder(); - for (int i = 0; i < chars.length(); i++) { - if (f.canDisplay(chars.codePointAt(i))) { - visibleChars.append(chars.charAt(i)); - } - } - individialSample.setText(visibleChars.toString()); - } - - private void updateCheckboxes() { - Font f = getSelectedFont().deriveFont(12f); - int rc = CharacterRanges.rangeCount(); - - Set allChars = new HashSet<>(); - for (int i = 0; i < rc; i++) { - rangeNames[i] = CharacterRanges.rangeName(i); - int codes[] = CharacterRanges.rangeCodes(i); - int avail = 0; - StringBuilder sample = new StringBuilder(); - for (int c = 0; c < codes.length; c++) { - if (f.canDisplay(codes[c])) { - allChars.add(codes[c]); - if (avail < SAMPLE_MAX_LENGTH) { - sample.append((char) codes[c]); - } - avail++; - } - } - rangeSamples[i].setText(sample.toString()); - rangeSamples[i].setFont(f); - rangeCheckboxes[i].setText(translate("range.description").replace("%available%", Integer.toString(avail)).replace("%name%", rangeNames[i]).replace("%total%", Integer.toString(codes.length))); - } - allCheckbox.setText(translate("allcharacters").replace("%available%", Integer.toString(allChars.size()))); - individialSample.setFont(f); - updateIndividual(); - } - - @Override - public void setVisible(boolean b) { - if (b) { - result = ERROR_OPTION; - } - - super.setVisible(b); - } - - private void okButtonActionPerformed(ActionEvent evt) { - result = OK_OPTION; - setVisible(false); - } - - private void cancelButtonActionPerformed(ActionEvent evt) { - result = CANCEL_OPTION; - setVisible(false); - } - - private void loadFromDiscButtonActionPerformed(ActionEvent evt) { - if (customFont != null) { - if (loadFromDisk()) { - updateCheckboxes(); - } - } - - ttfFileRadio.setSelected(true); - } - - private boolean loadFromDisk() { - JFileChooser fc = new JFileChooser(); - fc.setCurrentDirectory(new File(Configuration.lastOpenDir.get())); - FileFilter ttfFilter = new FileFilter() { - @Override - public boolean accept(File f) { - return (f.getName().toLowerCase().endsWith(".ttf")) || (f.isDirectory()); - } - - @Override - public String getDescription() { - return translate("filter.ttf"); - } - }; - fc.setFileFilter(ttfFilter); - fc.setAcceptAllFileFilterUsed(true); - JFrame f = new JFrame(); - View.setWindowIcon(f); - int returnVal = fc.showOpenDialog(f); - if (returnVal == JFileChooser.APPROVE_OPTION) { - Configuration.lastOpenDir.set(Helper.fixDialogFile(fc.getSelectedFile()).getParentFile().getAbsolutePath()); - File selfile = Helper.fixDialogFile(fc.getSelectedFile()); - try { - customFont = Font.createFont(Font.TRUETYPE_FONT, selfile); - ttfFileRadio.setText(translate("ttffile.selection").replace("%fontname%", customFont.getName()).replace("%filename%", selfile.getName())); - return true; - } catch (FontFormatException ex) { - JOptionPane.showMessageDialog(this, translate("error.invalidfontfile"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } catch (IOException ex) { - JOptionPane.showMessageDialog(this, translate("error.cannotreadfontfile"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } - } - return false; - } - - public int showDialog() { - setVisible(true); - return result; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.tags.font.CharacterRanges; +import com.jpexs.helpers.Helper; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.event.ActionEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.filechooser.FileFilter; + +/** + * + * @author JPEXS + */ +public class FontEmbedDialog extends AppDialog { + + private static final int SAMPLE_MAX_LENGTH = 50; + + private final JComboBox familyNamesSelection; + + private final JComboBox faceSelection; + + private final JCheckBox[] rangeCheckboxes; + + private final String rangeNames[]; + + private final JLabel[] rangeSamples; + + private final JTextField individualCharsField; + + private int result = ERROR_OPTION; + + private JLabel individialSample; + + private Font customFont; + + private final JCheckBox allCheckbox; + + public Font getSelectedFont() { + if (ttfFileRadio.isSelected() && customFont != null) { + return customFont; + } + return ((FontFace) faceSelection.getSelectedItem()).font; + } + + public Set getSelectedChars() { + Set chars = new TreeSet<>(); + Font f = getSelectedFont(); + if (allCheckbox.isSelected()) { + for (int i = 0; i < rangeCheckboxes.length; i++) { + int[] codes = CharacterRanges.rangeCodes(i); + for (int c : codes) { + if (f.canDisplay(c)) { + chars.add(c); + } + } + } + } else { + for (int i = 0; i < rangeCheckboxes.length; i++) { + if (rangeCheckboxes[i].isSelected()) { + int[] codes = CharacterRanges.rangeCodes(i); + for (int c : codes) { + if (f.canDisplay(c)) { + chars.add(c); + } + } + } + } + String indStr = individualCharsField.getText(); + for (int i = 0; i < indStr.length(); i++) { + if (f.canDisplay(indStr.codePointAt(i))) { + chars.add(indStr.codePointAt(i)); + } + } + } + return chars; + } + + private JRadioButton ttfFileRadio; + + private JRadioButton installedRadio; + + private void updateFaceSelection() { + faceSelection.setModel(FontPanel.getFaceModel((FontFamily) familyNamesSelection.getSelectedItem())); + } + + public FontEmbedDialog(FontFace selectedFace, String selectedChars) { + setSize(900, 600); + setDefaultCloseOperation(HIDE_ON_CLOSE); + setTitle(translate("dialog.title")); + + Container cnt = getContentPane(); + cnt.setLayout(new BoxLayout(cnt, BoxLayout.Y_AXIS)); + + JPanel selFontPanel = new JPanel(new FlowLayout()); + + installedRadio = new JRadioButton(translate("installed")); + ttfFileRadio = new JRadioButton(translate("ttffile.noselection")); + + ButtonGroup bg = new ButtonGroup(); + bg.add(installedRadio); + bg.add(ttfFileRadio); + + installedRadio.setSelected(true); + + individialSample = new JLabel(); + familyNamesSelection = new JComboBox<>(FontPanel.getFamilyModel()); + familyNamesSelection.setSelectedItem(new FontFamily(selectedFace.font)); + faceSelection = new JComboBox<>(); + updateFaceSelection(); + faceSelection.setSelectedItem(selectedFace); + JButton loadFromDiskButton = new JButton(View.getIcon("open16")); + loadFromDiskButton.setToolTipText(translate("button.loadfont")); + loadFromDiskButton.addActionListener(this::loadFromDiscButtonActionPerformed); + selFontPanel.add(installedRadio); + selFontPanel.add(familyNamesSelection); + selFontPanel.add(faceSelection); + selFontPanel.add(ttfFileRadio); + selFontPanel.add(loadFromDiskButton); + + installedRadio.addItemListener(new ItemListener() { + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + updateCheckboxes(); + } + } + }); + + ttfFileRadio.addItemListener(new ItemListener() { + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + if (ttfFileRadio.isSelected()) { + if (customFont == null) { + if (loadFromDisk()) { + updateCheckboxes(); + } else { + installedRadio.setSelected(true); + } + } else { + updateCheckboxes(); + } + } + } + } + }); + + cnt.add(selFontPanel); + JPanel rangesPanel = new JPanel(); + rangesPanel.setLayout(new BoxLayout(rangesPanel, BoxLayout.Y_AXIS)); + final int rc = CharacterRanges.rangeCount(); + rangeCheckboxes = new JCheckBox[rc]; + rangeSamples = new JLabel[rc]; + rangeNames = new String[rc]; + allCheckbox = new JCheckBox(translate("allcharacters")); + allCheckbox.addItemListener(new ItemListener() { + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + for (int i = 0; i < rc; i++) { + rangeCheckboxes[i].setEnabled(false); + } + individualCharsField.setEnabled(false); + } else if (e.getStateChange() == ItemEvent.DESELECTED) { + for (int i = 0; i < rc; i++) { + rangeCheckboxes[i].setEnabled(true); + } + individualCharsField.setEnabled(true); + } + } + }); + JPanel rangeRowPanel = new JPanel(); + rangeRowPanel.setLayout(new BorderLayout()); + rangeRowPanel.add(allCheckbox, BorderLayout.WEST); + rangeRowPanel.setAlignmentX(0); + rangesPanel.add(rangeRowPanel); + + for (int i = 0; i < rc; i++) { + rangeNames[i] = CharacterRanges.rangeName(i); + rangeSamples[i] = new JLabel(""); + rangeCheckboxes[i] = new JCheckBox(rangeNames[i]); + rangeRowPanel = new JPanel(); + rangeRowPanel.setLayout(new BoxLayout(rangeRowPanel, BoxLayout.X_AXIS)); + rangeRowPanel.add(rangeCheckboxes[i]); + rangeRowPanel.add(Box.createHorizontalGlue()); + rangeRowPanel.add(rangeSamples[i]); + rangeRowPanel.setAlignmentX(0); + rangesPanel.add(rangeRowPanel); + } + cnt.add(new JScrollPane(rangesPanel)); + + JPanel specialPanel = new JPanel(); + specialPanel.setLayout(new BoxLayout(specialPanel, BoxLayout.X_AXIS)); + specialPanel.add(new JLabel(translate("label.individual"))); + individualCharsField = new JTextField(); + individualCharsField.setPreferredSize(new Dimension(100, individualCharsField.getPreferredSize().height)); + individialSample = new JLabel(); + specialPanel.add(individualCharsField); + + cnt.add(specialPanel); + cnt.add(individialSample); + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + JButton okButton = new JButton(AppStrings.translate("button.ok")); + okButton.addActionListener(this::okButtonActionPerformed); + JButton cancelButton = new JButton(AppStrings.translate("button.cancel")); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + buttonsPanel.add(okButton); + buttonsPanel.add(cancelButton); + cnt.add(buttonsPanel); + View.setWindowIcon(this); + View.centerScreen(this); + setModalityType(ModalityType.APPLICATION_MODAL); + individualCharsField.setText(selectedChars); + getRootPane().setDefaultButton(okButton); + familyNamesSelection.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + updateFaceSelection(); + updateCheckboxes(); + } + }); + faceSelection.addItemListener((ItemEvent e) -> { + updateCheckboxes(); + }); + updateCheckboxes(); + individualCharsField.getDocument().addDocumentListener(new DocumentListener() { + + @Override + public void insertUpdate(DocumentEvent e) { + updateIndividual(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + updateIndividual(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + updateIndividual(); + } + }); + } + + private void updateIndividual() { + String chars = individualCharsField.getText(); + Font f = getSelectedFont(); + StringBuilder visibleChars = new StringBuilder(); + for (int i = 0; i < chars.length(); i++) { + if (f.canDisplay(chars.codePointAt(i))) { + visibleChars.append(chars.charAt(i)); + } + } + individialSample.setText(visibleChars.toString()); + } + + private void updateCheckboxes() { + Font f = getSelectedFont().deriveFont(12f); + int rc = CharacterRanges.rangeCount(); + + Set allChars = new HashSet<>(); + for (int i = 0; i < rc; i++) { + rangeNames[i] = CharacterRanges.rangeName(i); + int[] codes = CharacterRanges.rangeCodes(i); + int avail = 0; + StringBuilder sample = new StringBuilder(); + for (int c = 0; c < codes.length; c++) { + if (f.canDisplay(codes[c])) { + allChars.add(codes[c]); + if (avail < SAMPLE_MAX_LENGTH) { + sample.append((char) codes[c]); + } + avail++; + } + } + rangeSamples[i].setText(sample.toString()); + rangeSamples[i].setFont(f); + rangeCheckboxes[i].setText(translate("range.description").replace("%available%", Integer.toString(avail)).replace("%name%", rangeNames[i]).replace("%total%", Integer.toString(codes.length))); + } + allCheckbox.setText(translate("allcharacters").replace("%available%", Integer.toString(allChars.size()))); + individialSample.setFont(f); + updateIndividual(); + } + + @Override + public void setVisible(boolean b) { + if (b) { + result = ERROR_OPTION; + } + + super.setVisible(b); + } + + private void okButtonActionPerformed(ActionEvent evt) { + result = OK_OPTION; + setVisible(false); + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + result = CANCEL_OPTION; + setVisible(false); + } + + private void loadFromDiscButtonActionPerformed(ActionEvent evt) { + if (customFont != null) { + if (loadFromDisk()) { + updateCheckboxes(); + } + } + + ttfFileRadio.setSelected(true); + } + + private boolean loadFromDisk() { + JFileChooser fc = new JFileChooser(); + fc.setCurrentDirectory(new File(Configuration.lastOpenDir.get())); + FileFilter ttfFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return (f.getName().toLowerCase().endsWith(".ttf")) || (f.isDirectory()); + } + + @Override + public String getDescription() { + return translate("filter.ttf"); + } + }; + fc.setFileFilter(ttfFilter); + fc.setAcceptAllFileFilterUsed(true); + JFrame f = new JFrame(); + View.setWindowIcon(f); + int returnVal = fc.showOpenDialog(f); + if (returnVal == JFileChooser.APPROVE_OPTION) { + Configuration.lastOpenDir.set(Helper.fixDialogFile(fc.getSelectedFile()).getParentFile().getAbsolutePath()); + File selfile = Helper.fixDialogFile(fc.getSelectedFile()); + try { + customFont = Font.createFont(Font.TRUETYPE_FONT, selfile); + ttfFileRadio.setText(translate("ttffile.selection").replace("%fontname%", customFont.getName()).replace("%filename%", selfile.getName())); + return true; + } catch (FontFormatException ex) { + JOptionPane.showMessageDialog(this, translate("error.invalidfontfile"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } catch (IOException ex) { + JOptionPane.showMessageDialog(this, translate("error.cannotreadfontfile"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + } + return false; + } + + public int showDialog() { + setVisible(true); + return result; + } +} 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..b2c098ec7 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -1,1424 +1,1431 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui; - -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.LocalDataArea; -import com.jpexs.decompiler.flash.action.Stage; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.ecma.Undefined; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.gui.player.MediaDisplay; -import com.jpexs.decompiler.flash.gui.player.MediaDisplayListener; -import com.jpexs.decompiler.flash.gui.player.Zoom; -import com.jpexs.decompiler.flash.tags.DefineButtonSoundTag; -import com.jpexs.decompiler.flash.tags.DoActionTag; -import com.jpexs.decompiler.flash.tags.base.BoundedTag; -import com.jpexs.decompiler.flash.tags.base.ButtonTag; -import com.jpexs.decompiler.flash.tags.base.CharacterTag; -import com.jpexs.decompiler.flash.tags.base.DrawableTag; -import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; -import com.jpexs.decompiler.flash.tags.base.RenderContext; -import com.jpexs.decompiler.flash.tags.base.SoundTag; -import com.jpexs.decompiler.flash.tags.base.TextTag; -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.timeline.Timelined; -import com.jpexs.decompiler.flash.types.ConstantColorColorTransform; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.SOUNDINFO; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Cache; -import com.jpexs.helpers.SerializableImage; -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Cursor; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.Transparency; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionAdapter; -import java.awt.event.MouseMotionListener; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.awt.image.VolatileImage; -import java.io.IOException; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.sound.sampled.LineUnavailableException; -import javax.sound.sampled.UnsupportedAudioFileException; -import javax.swing.JLabel; -import javax.swing.JPanel; - -/** - * - * @author JPEXS - */ -public final class ImagePanel extends JPanel implements MediaDisplay { - - private static final Logger logger = Logger.getLogger(ImagePanel.class.getName()); - - private final List listeners = new ArrayList<>(); - - private Timelined timelined; - - private boolean stillFrame = false; - - private Timer timer; - - private int frame = -1; - - private boolean loop; - - private LocalDataArea lda; - - private boolean zoomAvailable = false; - - private SWF swf; - - private boolean loaded; - - private int mouseButton; - - private final JLabel debugLabel = new JLabel("-"); - - private Point cursorPosition = null; - - private MouseEvent lastMouseEvent = null; - - private final List soundPlayers = new ArrayList<>(); - - private final Cache displayObjectCache = Cache.getInstance(false, false, "displayObject"); - - private final IconPanel iconPanel; - - private int time = 0; - - private int selectedDepth = -1; - - private Zoom zoom = new Zoom(); - - private final Object delayObject = new Object(); - - private boolean drawReady; - - private final int drawWaitLimit = 50; // ms - - private TextTag textTag; - - private TextTag newTextTag; - - private int msPerFrame; - - private final boolean lowQuality = false; - - private final double LQ_FACTOR = 2; - - public synchronized void selectDepth(int depth) { - if (depth != selectedDepth) { - this.selectedDepth = depth; - } - - hideMouseSelection(); - } - - public void fireMediaDisplayStateChanged() { - for (MediaDisplayListener l : listeners) { - l.mediaDisplayStateChanged(this); - } - } - - @Override - public void addEventListener(MediaDisplayListener listener) { - listeners.add(listener); - } - - @Override - public void removeEventListener(MediaDisplayListener listener) { - listeners.remove(listener); - } - - private class IconPanel extends JPanel { - - private SerializableImage _img; - - private Rectangle _rect = null; - - private ButtonTag mouseOverButton = null; - - private boolean autoFit = false; - - private boolean allowMove = true; - - private Point dragStart = null; - - private Point offsetPoint = new Point(0, 0); - - private synchronized SerializableImage getImg() { - return _img; - } - - public synchronized Rectangle getRect() { - return _rect; - } - - public boolean hasAllowMove() { - return allowMove; - } - - VolatileImage renderImage; - - public void render() { - SerializableImage img = getImg(); - Rectangle rect = getRect(); - if (img == null) { - return; - } - Graphics2D g2 = null; - do { - - int valid = renderImage.validate(View.getDefaultConfiguration()); - - if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { - renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); - } - - try { - g2 = renderImage.createGraphics(); - g2.setPaint(View.transparentPaint); - g2.fill(new Rectangle(0, 0, getWidth(), getHeight())); - g2.setComposite(AlphaComposite.SrcOver); - g2.setPaint(View.getSwfBackgroundColor()); - g2.fill(new Rectangle(0, 0, getWidth(), getHeight())); - - g2.setComposite(AlphaComposite.SrcOver); - if (rect != null) { - g2.drawImage(img.getBufferedImage(), rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, 0, 0, img.getWidth(), img.getHeight(), null); - } - } finally { - if (g2 != null) { - g2.dispose(); - } - } - - } while (renderImage.contentsLost()); - } - - public IconPanel() { - addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); - if (_img != null) { - calcRect(); - render(); - } - repaint(); - } - - }); - addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON1) { - dragStart = e.getPoint(); - } - } - - @Override - public void mouseReleased(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON1) { - dragStart = null; - } - } - - }); - addMouseMotionListener(new MouseMotionAdapter() { - @Override - public void mouseDragged(MouseEvent e) { - if (dragStart != null && allowMove) { - Point dragEnd = e.getPoint(); - Point delta = new Point(dragEnd.x - dragStart.x, dragEnd.y - dragStart.y); - offsetPoint.x += delta.x; - offsetPoint.y += delta.y; - dragStart = dragEnd; - repaint(); - } - } - - }); - } - - public void setAutoFit(boolean autoFit) { - this.autoFit = autoFit; - repaint(); - } - - public synchronized BufferedImage getLastImage() { - if (_img == null) { - return null; - } - return _img.getBufferedImage(); - } - - public synchronized void setImg(SerializableImage img) { - this._img = img; - if (img != null) { - calcRect(); - render(); - } - repaint(); - } - - public synchronized Point toImagePoint(Point p) { - if (_img == null) { - return null; - } - - return new Point((p.x - _rect.x) * _img.getWidth() / _rect.width, (p.y - _rect.y) * _img.getHeight() / _rect.height); - } - - private void setAllowMove(boolean allowMove) { - this.allowMove = allowMove; - if (!allowMove) { - offsetPoint = new Point(); - } - } - - private synchronized void calcRect() { - if (_img != null) { - int w1 = (int) (_img.getWidth() * (lowQuality ? LQ_FACTOR : 1)); - int h1 = (int) (_img.getHeight() * (lowQuality ? LQ_FACTOR : 1)); - - int w2 = getWidth(); - int h2 = getHeight(); - - int w; - int h; - if (autoFit) { - if (w1 <= w2 && h1 <= h2) { - w = w1; - h = h1; - } else { - - h = h1 * w2 / w1; - if (h > h2) { - w = w1 * h2 / h1; - h = h2; - } else { - w = w2; - } - } - } else { - w = w1; - h = h1; - } - - setAllowMove(h > h2 || w > w2); - _rect = new Rectangle(getWidth() / 2 - w / 2 + offsetPoint.x, getHeight() / 2 - h / 2 + offsetPoint.y, w, h); - } else { - _rect = null; - } - } - - @Override - protected void paintComponent(Graphics g) { - Graphics2D g2d = (Graphics2D) g; - - if (renderImage != null) { - calcRect(); - if (renderImage.validate(View.getDefaultConfiguration()) != VolatileImage.IMAGE_OK) { - renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); - render(); - } - - if (renderImage != null) { - g2d.drawImage(renderImage, 0, 0, null); - } - } - g2d.setColor(Color.red); - - DecimalFormat df = new DecimalFormat(); - df.setMaximumFractionDigits(2); - df.setMinimumFractionDigits(0); - df.setGroupingUsed(false); - - float frameLoss = 100 - (getFpsIs() / fpsShouldBe * 100); - - if (Configuration._debugMode.get()) { - g2d.drawString("frameLoss:" + df.format(frameLoss) + "%", 20, 20); - } - } - } - - @Override - public void setBackground(Color bg) { - if (iconPanel != null) { - iconPanel.setBackground(bg); - } - super.setBackground(bg); - } - - @Override - public synchronized void addMouseListener(MouseListener l) { - iconPanel.addMouseListener(l); - } - - @Override - public synchronized void removeMouseListener(MouseListener l) { - iconPanel.removeMouseListener(l); - } - - @Override - public synchronized void addMouseMotionListener(MouseMotionListener l) { - iconPanel.addMouseMotionListener(l); - } - - @Override - public synchronized void removeMouseMotionListener(MouseMotionListener l) { - iconPanel.removeMouseMotionListener(l); - } - - private void updatePos(Timelined timelined, MouseEvent lastMouseEvent, Timer thisTimer) { - if (timelined != null) { - - BoundedTag bounded = (BoundedTag) timelined; - RECT rect = bounded.getRect(); - int width = rect.getWidth(); - double scale = 1.0; - /*if (width > swf.displayRect.getWidth()) { - scale = (double) swf.displayRect.getWidth() / (double) width; - }*/ - Matrix m = Matrix.getTranslateInstance(-rect.Xmin, -rect.Ymin); - m.scale(scale); - - Point p = lastMouseEvent == null ? null : lastMouseEvent.getPoint(); - - synchronized (ImagePanel.class) { - if (timer == thisTimer) { - cursorPosition = p; - } - } - } - } - - private void showSelectedName() { - if (selectedDepth > -1 && frame > -1) { - DepthState ds = timelined.getTimeline().getFrame(frame).layers.get(selectedDepth); - if (ds != null) { - CharacterTag cht = timelined.getTimeline().swf.getCharacter(ds.characterId); - if (cht != null) { - debugLabel.setText(cht.getName()); - } - } - } - } - - public void hideMouseSelection() { - if (selectedDepth > -1) { - showSelectedName(); - } else { - debugLabel.setText(" - "); - } - } - - public ImagePanel() { - super(new BorderLayout()); - //iconPanel.setHorizontalAlignment(JLabel.CENTER); - setOpaque(true); - setBackground(View.getDefaultBackgroundColor()); - - loop = true; - iconPanel = new IconPanel(); - //labelPan.add(label, new GridBagConstraints()); - add(iconPanel, BorderLayout.CENTER); - add(debugLabel, BorderLayout.NORTH); - iconPanel.addMouseListener(new MouseAdapter() { - - @Override - public void mouseEntered(MouseEvent e) { - synchronized (ImagePanel.class) { - lastMouseEvent = e; - redraw(); - } - } - - @Override - public void mouseExited(MouseEvent e) { - synchronized (ImagePanel.class) { - lastMouseEvent = null; - hideMouseSelection(); - redraw(); - } - } - - @Override - public void mousePressed(MouseEvent e) { - synchronized (ImagePanel.class) { - mouseButton = e.getButton(); - lastMouseEvent = e; - redraw(); - ButtonTag button = iconPanel.mouseOverButton; - if (button != null) { - DefineButtonSoundTag sounds = button.getSounds(); - if (sounds != null && sounds.buttonSoundChar2 != 0) { // OverUpToOverDown - playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar2), sounds.buttonSoundInfo2, timer); - } - } - } - } - - @Override - public void mouseReleased(MouseEvent e) { - synchronized (ImagePanel.class) { - mouseButton = 0; - lastMouseEvent = e; - redraw(); - ButtonTag button = iconPanel.mouseOverButton; - if (button != null) { - DefineButtonSoundTag sounds = button.getSounds(); - if (sounds != null && sounds.buttonSoundChar3 != 0) { // OverDownToOverUp - playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar3), sounds.buttonSoundInfo3, timer); - } - } - } - } - - }); - iconPanel.addMouseMotionListener(new MouseMotionAdapter() { - @Override - public void mouseMoved(MouseEvent e) { - synchronized (ImagePanel.class) { - lastMouseEvent = e; - redraw(); - } - } - - @Override - public void mouseDragged(MouseEvent e) { - synchronized (ImagePanel.class) { - lastMouseEvent = e; - redraw(); - } - } - - }); - } - - private synchronized void redraw() { - if (timer == null && timelined != null) { - startTimer(timelined.getTimeline(), false); - } - } - - @Override - public synchronized void zoom(Zoom zoom) { - boolean modified = this.zoom.value != zoom.value || this.zoom.fit != zoom.fit; - if (modified) { - this.zoom = zoom; - displayObjectCache.clear(); - redraw(); - if (textTag != null) { - setText(textTag, newTextTag); - } - - fireMediaDisplayStateChanged(); - } - } - - @Override - public synchronized BufferedImage printScreen() { - return iconPanel.getLastImage(); - } - - @Override - public synchronized double getZoomToFit() { - if (timelined != null) { - RECT bounds = timelined.getRect(); - double w1 = bounds.getWidth() / SWF.unitDivisor; - double h1 = bounds.getHeight() / SWF.unitDivisor; - - double w2 = getWidth(); - double h2 = getHeight(); - - double w; - double h; - h = h1 * w2 / w1; - if (h > h2) { - w = w1 * h2 / h1; - } else { - w = w2; - } - - if (w1 <= Double.MIN_NORMAL) { - return 1.0; - } - - return (double) w / (double) w1; - } - - return 1; - } - - @Override - public synchronized boolean zoomAvailable() { - return zoomAvailable; - } - - public void setTimelined(final Timelined drawable, final SWF swf, int frame) { - Stage stage = new Stage(drawable) { - @Override - public void callFrame(int frame) { - executeFrame(frame); - } - - @Override - public Object callFunction(long functionAddress, long functionLength, List args, Map regNames, Object thisObj) { - try { - SWFInputStream sis = new SWFInputStream(swf, swf.uncompressedData, functionAddress, (int) (functionAddress + functionLength)); - return execute(sis); - } catch (IOException ex) { - Logger.getLogger(ImagePanel.class.getName()).log(Level.SEVERE, null, ex); - } - return Undefined.INSTANCE; - } - - @Override - public int getCurrentFrame() { - return ImagePanel.this.getCurrentFrame(); - } - - @Override - public int getTotalFrames() { - return ImagePanel.this.getTotalFrames(); - } - - @Override - public void gotoFrame(int frame) { - ImagePanel.this.pause(); - ImagePanel.this.gotoFrame(frame); - } - - @Override - public void gotoLabel(String label) { - //TODO - } - - @Override - public void pause() { - ImagePanel.this.pause(); - } - - @Override - public void play() { - ImagePanel.this.play(); - } - - @Override - public void trace(Object... val) { - for (Object o : val) { - System.out.println("trace:" + o.toString()); - } - } - - }; - lda = new LocalDataArea(stage); - synchronized (ImagePanel.class) { - stopInternal(); - if (drawable instanceof ButtonTag) { - frame = ButtonTag.FRAME_UP; - } - - displayObjectCache.clear(); - this.timelined = drawable; - this.swf = swf; - zoomAvailable = true; - timer = null; - if (frame > -1) { - this.frame = frame; - this.stillFrame = true; - } else { - this.frame = 0; - this.stillFrame = false; - } - - loaded = true; - - if (drawable.getTimeline().getFrameCount() == 0) { - clearImagePanel(); - return; - } - - time = 0; - drawReady = false; - redraw(); - play(); - } - - synchronized (delayObject) { - try { - delayObject.wait(drawWaitLimit); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - - synchronized (ImagePanel.class) { - if (!drawReady) { - clearImagePanel(); - } - } - - fireMediaDisplayStateChanged(); - } - - public synchronized void setImage(SerializableImage image) { - lda = null; - setBackground(View.getSwfBackgroundColor()); - clear(); - - timelined = null; - loaded = true; - stillFrame = true; - zoomAvailable = false; - iconPanel.setImg(image); - drawReady = true; - - fireMediaDisplayStateChanged(); - } - - public synchronized void setText(TextTag textTag, TextTag newTextTag) { - setBackground(View.getSwfBackgroundColor()); - clear(); - - lda = null; - timelined = null; - loaded = true; - stillFrame = true; - zoomAvailable = true; - - this.textTag = textTag; - this.newTextTag = newTextTag; - - double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; - - RECT rect = textTag.getRect(); - int width = (int) (rect.getWidth() * zoomDouble); - int height = (int) (rect.getHeight() * zoomDouble); - SerializableImage image = new SerializableImage((int) (width / SWF.unitDivisor) + 1, - (int) (height / SWF.unitDivisor) + 1, SerializableImage.TYPE_INT_ARGB); - image.fillTransparent(); - Matrix m = Matrix.getTranslateInstance(-rect.Xmin * zoomDouble, -rect.Ymin * zoomDouble); - m.scale(zoomDouble); - textTag.toImage(0, 0, 0, new RenderContext(), image, false, m, m, m, new ConstantColorColorTransform(0xFFC0C0C0)); - - if (newTextTag != null) { - newTextTag.toImage(0, 0, 0, new RenderContext(), image, false, m, m, m, new ConstantColorColorTransform(0xFF000000)); - } - - iconPanel.setImg(image); - drawReady = true; - - fireMediaDisplayStateChanged(); - } - - private synchronized void clearImagePanel() { - iconPanel.setImg(null); - } - - @Override - public synchronized int getCurrentFrame() { - return frame; - } - - @Override - public synchronized int getTotalFrames() { - if (timelined == null) { - return 0; - } - if (stillFrame) { - return 0; - } - return timelined.getTimeline().getFrameCount(); - } - - @Override - public void pause() { - stopInternal(); - redraw(); - } - - @Override - public void stop() { - stopInternal(); - rewind(); - redraw(); - } - - @Override - public void close() throws IOException { - stopInternal(); - } - - private void stopAllSounds() { - for (int i = soundPlayers.size() - 1; i >= 0; i--) { - SoundTagPlayer pl = soundPlayers.get(i); - pl.close(); - } - soundPlayers.clear(); - } - - private void clear() { - if (timer != null) { - timer.cancel(); - timer = null; - fireMediaDisplayStateChanged(); - } - - textTag = null; - newTextTag = null; - displayObjectCache.clear(); - } - - private void nextFrame(Timer thisTimer, final int cnt, final int timeShouldBe) { - drawFrame(thisTimer, true); - - synchronized (ImagePanel.class) { - if (timelined != null && timer == thisTimer) { - int frameCount = timelined.getTimeline().getFrameCount(); - - int oldFrame = frame; - for (int i = 0; i < cnt; i++) { - if (!stillFrame && frameCount > 0) { - frame = (frame + 1) % frameCount; - } - - if (!stillFrame && frame == frameCount - 1 && !loop) { - stopInternal(); - return; - } - - if (i < cnt - 1) { //skip not displayed frames, do not display, only play sounds, etc. - drawFrame(thisTimer, false); - } - } - if (frame != oldFrame) { - if (frame == 0) { - stopAllSounds(); - } - time = 0; - } else { - time = timeShouldBe; - } - } - } - - fireMediaDisplayStateChanged(); - } - - private static SerializableImage getFrame(SWF swf, int frame, int time, Timelined drawable, RenderContext renderContext, int selectedDepth, double zoom) { - Timeline timeline = drawable.getTimeline(); - //int mouseButton = renderContext.mouseButton; - //Point cursorPosition = renderContext.cursorPosition; - //String key = "drawable_" + frame + "_" + drawable.hashCode() + "_" + mouseButton + "_depth" + selectedDepth + "_" + (cursorPosition == null ? "out" : cursorPosition.hashCode()) + "_" + zoom + "_" + timeline.fontFrameNum; - SerializableImage img; - //SerializableImage img = swf.getFromCache(key); - //if (img == null) { - //boolean shouldCache = timeline.isSingleFrame(frame); - RECT rect = drawable.getRect(); - - int width = (int) (rect.getWidth() * zoom); - int height = (int) (rect.getHeight() * zoom); - SerializableImage image = new SerializableImage((int) Math.ceil(width / SWF.unitDivisor), - (int) Math.ceil(height / SWF.unitDivisor), SerializableImage.TYPE_INT_ARGB); - //renderContext.borderImage = new SerializableImage(image.getWidth(), image.getHeight(), SerializableImage.TYPE_INT_ARGB); - image.fillTransparent(); - Matrix m = new Matrix(); - m.translate(-rect.Xmin * zoom, -rect.Ymin * zoom); - m.scale(zoom); - timeline.toImage(frame, time, renderContext, image, false, m, m, m, null); - - Graphics2D gg = (Graphics2D) image.getGraphics(); - gg.setStroke(new BasicStroke(3)); - gg.setPaint(Color.green); - gg.setTransform(AffineTransform.getTranslateInstance(0, 0)); - DepthState ds = null; - if (timeline.getFrameCount() > frame) { - ds = timeline.getFrame(frame).layers.get(selectedDepth); - } - - if (ds != null) { - CharacterTag cht = swf.getCharacter(ds.characterId); - if (cht != null) { - if (cht instanceof DrawableTag) { - DrawableTag dt = (DrawableTag) cht; - int drawableFrameCount = dt.getNumFrames(); - if (drawableFrameCount == 0) { - drawableFrameCount = 1; - } - - int dframe = time % drawableFrameCount; - Shape outline = dt.getOutline(dframe, time, ds.ratio, renderContext, Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m.concatenate(new Matrix(ds.matrix))), true); - Rectangle bounds = outline.getBounds(); - gg.setStroke(new BasicStroke(2.0f, - BasicStroke.CAP_BUTT, - BasicStroke.JOIN_MITER, - 10.0f, new float[]{10.0f}, 0.0f)); - gg.setPaint(Color.red); - gg.draw(bounds); - } - } - } - - img = image; - - /*if (shouldCache) { - swf.putToCache(key, img); - }*/ - //} - return img; - } - - private Object execute(SWFInputStream sis) throws IOException { - if (!Configuration.internalFlashViewerExecuteAs12.get()) { - return Undefined.INSTANCE; - } - if (lda == null) { - return Undefined.INSTANCE; - } - long ip = sis.getPos(); - //System.err.println("============="); - Action a; - while ((a = sis.readAction()) != null) { - int actionLengthWithHeader = a.getTotalActionLength(); - a.setAddress(ip); - a.execute(lda); - /*System.err.print("" + a + ", stack: ["); - for (Object o : lda.stack) { - System.err.print("" + o + ","); - } - System.err.println("]");*/ - if (lda.returnValue != null) { - return lda.returnValue; - } - if (lda.jump != null) { - ip = lda.jump; - lda.jump = null; - } else { - ip += actionLengthWithHeader; - } - sis.seek(ip); - } - return Undefined.INSTANCE; - } - - private void executeFrame(int frame) { - if (!Configuration.internalFlashViewerExecuteAs12.get()) { - return; - } - if (timelined == null) { - return; - } - Frame f = timelined.getTimeline().getFrame(frame); - List actions = f.actions; - if (lda != null) { - lda.clear(); - } - for (DoActionTag src : actions) { - try { - ByteArrayRange actionBytes = src.getActionBytes(); - int prevLength = actionBytes.getPos(); - SWFInputStream rri = new SWFInputStream(swf, actionBytes.getArray(), 0, prevLength + actionBytes.getLength()); - if (prevLength != 0) { - rri.seek(prevLength); - } - execute(rri); - } catch (IOException ex) { - Logger.getLogger(ImagePanel.class.getName()).log(Level.SEVERE, null, ex); - } - - } - } - - private void drawFrame(Timer thisTimer, boolean display) { - Timelined timelined; - MouseEvent lastMouseEvent; - int frame; - int time; - Point cursorPosition; - int mouseButton; - int selectedDepth; - Zoom zoom; - SWF swf; - - synchronized (ImagePanel.class) { - timelined = this.timelined; - lastMouseEvent = this.lastMouseEvent; - } - - synchronized (ImagePanel.class) { - frame = this.frame; - time = this.time; - cursorPosition = this.cursorPosition; - if (cursorPosition != null) { - cursorPosition = iconPanel.toImagePoint(cursorPosition); - } - - mouseButton = this.mouseButton; - selectedDepth = this.selectedDepth; - zoom = this.zoom; - swf = this.swf; - } - - if (timelined == null) { - return; - } - - RenderContext renderContext = new RenderContext(); - renderContext.displayObjectCache = displayObjectCache; - if (cursorPosition != null) { - renderContext.cursorPosition = new Point((int) (cursorPosition.x * SWF.unitDivisor), (int) (cursorPosition.y * SWF.unitDivisor)); - } - - renderContext.mouseButton = mouseButton; - renderContext.stateUnderCursor = new ArrayList<>(); - - SerializableImage img; - try { - Timeline timeline = timelined.getTimeline(); - if (frame >= timeline.getFrameCount()) { - return; - } - - double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; - if (lowQuality) { - zoomDouble /= LQ_FACTOR; - } - updatePos(timelined, lastMouseEvent, thisTimer); - - Matrix mat = new Matrix(); - mat.translateX = swf.displayRect.Xmin; - mat.translateY = swf.displayRect.Ymin; - - img = null; - if (display) { - img = getFrame(swf, frame, time, timelined, renderContext, selectedDepth, zoomDouble); - if (renderContext.borderImage != null) { - img = renderContext.borderImage; - } - } - - List sounds = new ArrayList<>(); - List soundClasses = new ArrayList<>(); - timeline.getSounds(frame, time, renderContext.mouseOverButton, mouseButton, sounds, soundClasses); - for (int cid : swf.getCharacters().keySet()) { - CharacterTag c = swf.getCharacter(cid); - for (String cls : soundClasses) { - if (cls.equals(c.getClassName())) { - sounds.add(cid); - } - } - } - - for (int sndId : sounds) { - CharacterTag c = swf.getCharacter(sndId); - if (c instanceof SoundTag) { - SoundTag st = (SoundTag) c; - playSound(st, null, thisTimer); - } - } - executeFrame(frame); - } catch (Throwable ex) { - // swf was closed during the rendering probably - return; - } - if (display) { - - StringBuilder ret = new StringBuilder(); - - if (cursorPosition != null) { - ret.append(" [").append(cursorPosition.x).append(",").append(cursorPosition.y).append("] : "); - } - - boolean handCursor = renderContext.mouseOverButton != null; - boolean first = true; - for (int i = renderContext.stateUnderCursor.size() - 1; i >= 0; i--) { - DepthState ds = renderContext.stateUnderCursor.get(i); - if (!first) { - ret.append(", "); - } - - first = false; - CharacterTag c = swf.getCharacter(ds.characterId); - ret.append(c.toString()); - } - - if (first) { - ret.append(" - "); - } - - ButtonTag lastMouseOverButton; - synchronized (ImagePanel.class) { - if (timer == thisTimer) { - iconPanel.setImg(img); - lastMouseOverButton = iconPanel.mouseOverButton; - iconPanel.mouseOverButton = renderContext.mouseOverButton; - debugLabel.setText(ret.toString()); - if (handCursor) { - iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } else if (iconPanel.hasAllowMove()) { - iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); - } else { - iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - - if (lastMouseOverButton != renderContext.mouseOverButton) { - ButtonTag b = renderContext.mouseOverButton; - if (b != null) { - // New mouse entered - DefineButtonSoundTag sounds = b.getSounds(); - if (sounds != null && sounds.buttonSoundChar1 != 0) { // IdleToOverUp - playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar1), sounds.buttonSoundInfo1, timer); - } - } - - b = lastMouseOverButton; - if (b != null) { - // Old mouse leave - DefineButtonSoundTag sounds = b.getSounds(); - if (sounds != null && sounds.buttonSoundChar0 != 0) { // OverUpToIdle - playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar0), sounds.buttonSoundInfo0, timer); - } - } - } - - drawReady = true; - synchronized (delayObject) { - delayObject.notify(); - } - } - } - } - } - - private void playSound(SoundTag st, SOUNDINFO soundInfo, Timer thisTimer) { - final SoundTagPlayer sp; - try { - int loopCount = 1; - if (soundInfo != null && soundInfo.hasLoops) { - loopCount = Math.max(1, soundInfo.loopCount); - } - - sp = new SoundTagPlayer(st, loopCount, false); - sp.addEventListener(new MediaDisplayListener() { - - @Override - public void mediaDisplayStateChanged(MediaDisplay source) { - } - - @Override - public void playingFinished(MediaDisplay source) { - synchronized (ImagePanel.class) { - sp.close(); - soundPlayers.remove(sp); - } - } - }); - - synchronized (ImagePanel.class) { - if (timer != null && timer == thisTimer) { - soundPlayers.add(sp); - sp.play(); - } else { - sp.close(); - } - } - } catch (LineUnavailableException | IOException | UnsupportedAudioFileException ex) { - logger.log(Level.SEVERE, "Error during playing sound", ex); - } - } - - public synchronized void clearAll() { - stopInternal(); - clearImagePanel(); - timelined = null; - swf = null; - - fireMediaDisplayStateChanged(); - } - - private synchronized void stopInternal() { - clear(); - stopAllSounds(); - } - - @Override - public synchronized void play() { - stopInternal(); - if (timelined != null) { - Timeline timeline = timelined.getTimeline(); - if (!stillFrame && frame == timeline.getFrameCount() - 1) { - frame = 0; - } - - startTimer(timeline, true); - } - } - - private synchronized void setMsPerFrame(int val) { - this.msPerFrame = val; - } - - private synchronized int getMsPerFrame() { - return this.msPerFrame; - } - - private long startRun = 0L; - - private final long startDrop = 0L; - - private int skippedFrames = 0; - - private float fpsShouldBe = 0; - - private float fpsIs = 0; - - private Timer fpsTimer; - - private int startFrame = 0; - - private synchronized void setFpsIs(float val) { - fpsIs = val; - } - - private synchronized float getFpsIs() { - return fpsIs; - } - - private synchronized void setSkippedFrames(int val) { - skippedFrames = val; - } - - private synchronized void addSkippedFrames(int val) { - skippedFrames += val; - } - - private synchronized int getSkippedFrames() { - return skippedFrames; - } - - private synchronized int getAndResetSkippedFrames() { - int ret = skippedFrames; - skippedFrames = 0; - return ret; - } - - private void scheduleTask(boolean singleFrame, long msDelay) { - TimerTask task = new TimerTask() { - public final Timer thisTimer = timer; - - public final boolean isSingleFrame = singleFrame; - - private long lastRun = 0L; - - @Override - public void run() { - try { - synchronized (ImagePanel.class) { - if (timer != thisTimer) { - return; - } - } - lastRun = System.currentTimeMillis(); - int curFrame = frame; - long delay = getMsPerFrame(); - if (isSingleFrame) { - drawFrame(thisTimer, true); - synchronized (ImagePanel.class) { - thisTimer.cancel(); - if (timer == thisTimer) { - timer = null; - } - } - - fireMediaDisplayStateChanged(); - } else { - //Time before drawing current frame - long frameTimeMsIs = System.currentTimeMillis(); - //Total number of frames in this timeline - int frameCount = timelined.getTimeline().getFrameCount(); - //How many ticks (= times where frame should be displayed in framerate) are there from hitting play button - int ticksFromStart = (int) Math.floor((frameTimeMsIs - startRun) / (double) getMsPerFrame()) + 1; - //Add ticks to first frame when hitting play button, ignoring total framecount => this value can be larger than number of frames in timeline - int frameOverMaxShouldBeNow = startFrame + ticksFromStart; - //Apply maximum frames repating, this is actual frame which should be drawed now - int frameShouldBeNow = frameOverMaxShouldBeNow % frameCount; - - //How many frames are there between last displayed frame and now. For perfect display(=no framedrop), value should be 1 - int skipFrames = frameShouldBeNow - curFrame; - //It is negative for some reason, this will display older frames. Add frameCount to stay in modulu framecount. - if (skipFrames < 0) { - skipFrames += frameCount; - } - //Change for more than 1 frame - if (skipFrames > 1) { - addSkippedFrames(skipFrames - 1); //drop those frames, draw only last one - } - //Frame "time" - ticks in current frame - int currentFrameTicks = 0; - if (frameCount == 1) { //We have only one frame, so the ticks on that frame equal ticks on whole timeline - currentFrameTicks = ticksFromStart; - } - nextFrame(thisTimer, skipFrames, currentFrameTicks); - - long afterDrawFrameTimeMsIs = System.currentTimeMillis(); - - int nextFrameOverMax = frameOverMaxShouldBeNow; - while (delay < 0) { //while the frame time already passed - nextFrameOverMax++; - long nextFrameOverMaxTimeMsShouldBe = startRun + getMsPerFrame() * nextFrameOverMax; - delay = nextFrameOverMaxTimeMsShouldBe - afterDrawFrameTimeMsIs; - } - } - //schedule next run of the task - scheduleTask(isSingleFrame, delay); - - } catch (Exception ex) { - logger.log(Level.SEVERE, "Frame drawing error", ex); - } - } - }; - if (timer != null) { - timer.schedule(task, msDelay); - } - } - - private synchronized void startTimer(Timeline timeline, boolean playing) { - - startRun = System.currentTimeMillis(); - startFrame = frame; - float frameRate = timeline.frameRate; - setMsPerFrame(frameRate == 0 ? 1000 : (int) (1000.0 / frameRate)); - final boolean singleFrame = !playing - || (stillFrame && timeline.isSingleFrame(frame)) - || (!stillFrame && timeline.getRealFrameCount() <= 1 && timeline.isSingleFrame()); - - if (fpsTimer == null) { - fpsTimer = new Timer(); - fpsTimer.schedule(new TimerTask() { - @Override - public void run() { - float skipped = getAndResetSkippedFrames(); - setFpsIs(fpsShouldBe - skipped); - } - }, 1000, 1000); - } - timer = new Timer(); - fpsShouldBe = timeline.frameRate; - fpsIs = fpsShouldBe; - scheduleTask(singleFrame, 0); - - } - - @Override - public synchronized void rewind() { - frame = 0; - fireMediaDisplayStateChanged(); - } - - @Override - public synchronized boolean isPlaying() { - if (timelined == null || stillFrame) { - return false; - } - - return (timelined.getTimeline().getFrameCount() <= 1) || (timer != null); - } - - @Override - public void setLoop(boolean loop) { - this.loop = loop; - } - - @Override - public synchronized void gotoFrame(int frame) { - if (timelined == null) { - return; - } - Timeline timeline = timelined.getTimeline(); - if (frame >= timeline.getFrameCount()) { - return; - } - if (frame < 0) { - return; - } - - this.frame = frame; - stopInternal(); - redraw(); - fireMediaDisplayStateChanged(); - } - - @Override - public synchronized float getFrameRate() { - if (timelined == null) { - return 1; - } - if (stillFrame) { - return 1; - } - return timelined.getTimeline().frameRate; - } - - @Override - public synchronized boolean isLoaded() { - return loaded; - } - - @Override - public boolean loopAvailable() { - return false; - } - - @Override - public boolean screenAvailable() { - return true; - } - - @Override - public synchronized Zoom getZoom() { - return zoom; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +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.LocalDataArea; +import com.jpexs.decompiler.flash.action.Stage; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.ecma.Undefined; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.gui.player.MediaDisplay; +import com.jpexs.decompiler.flash.gui.player.MediaDisplayListener; +import com.jpexs.decompiler.flash.gui.player.Zoom; +import com.jpexs.decompiler.flash.tags.DefineButtonSoundTag; +import com.jpexs.decompiler.flash.tags.DoActionTag; +import com.jpexs.decompiler.flash.tags.base.BoundedTag; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.DrawableTag; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.decompiler.flash.tags.base.RenderContext; +import com.jpexs.decompiler.flash.tags.base.SoundTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +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.timeline.Timelined; +import com.jpexs.decompiler.flash.types.ConstantColorColorTransform; +import com.jpexs.decompiler.flash.types.RECT; +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; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Transparency; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionAdapter; +import java.awt.event.MouseMotionListener; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.VolatileImage; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.swing.JLabel; +import javax.swing.JPanel; + +/** + * + * @author JPEXS + */ +public final class ImagePanel extends JPanel implements MediaDisplay { + + private static final Logger logger = Logger.getLogger(ImagePanel.class.getName()); + + private final List listeners = new ArrayList<>(); + + private Timelined timelined; + + private boolean stillFrame = false; + + private Timer timer; + + private int frame = -1; + + private boolean loop; + + private LocalDataArea lda; + + private boolean zoomAvailable = false; + + private SWF swf; + + private boolean loaded; + + private int mouseButton; + + private final JLabel debugLabel = new JLabel("-"); + + private Point cursorPosition = null; + + private MouseEvent lastMouseEvent = null; + + private final List soundPlayers = new ArrayList<>(); + + private final Cache displayObjectCache = Cache.getInstance(false, false, "displayObject"); + + private final IconPanel iconPanel; + + private int time = 0; + + private int selectedDepth = -1; + + private Zoom zoom = new Zoom(); + + private final Object delayObject = new Object(); + + private boolean drawReady; + + private final int drawWaitLimit = 50; // ms + + private TextTag textTag; + + private TextTag newTextTag; + + private int msPerFrame; + + private final boolean lowQuality = false; + + private final double LQ_FACTOR = 2; + + public synchronized void selectDepth(int depth) { + if (depth != selectedDepth) { + this.selectedDepth = depth; + } + + hideMouseSelection(); + } + + public void fireMediaDisplayStateChanged() { + for (MediaDisplayListener l : listeners) { + l.mediaDisplayStateChanged(this); + } + } + + @Override + public void addEventListener(MediaDisplayListener listener) { + listeners.add(listener); + } + + @Override + public void removeEventListener(MediaDisplayListener listener) { + listeners.remove(listener); + } + + private class IconPanel extends JPanel { + + private SerializableImage _img; + + private Rectangle _rect = null; + + private ButtonTag mouseOverButton = null; + + private boolean autoFit = false; + + private boolean allowMove = true; + + private Point dragStart = null; + + private Point offsetPoint = new Point(0, 0); + + private synchronized SerializableImage getImg() { + return _img; + } + + public synchronized Rectangle getRect() { + return _rect; + } + + public boolean hasAllowMove() { + return allowMove; + } + + VolatileImage renderImage; + + public void render() { + SerializableImage img = getImg(); + Rectangle rect = getRect(); + if (img == null) { + return; + } + Graphics2D g2 = null; + do { + + int valid = renderImage.validate(View.getDefaultConfiguration()); + + if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { + renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); + } + + try { + g2 = renderImage.createGraphics(); + g2.setPaint(View.transparentPaint); + g2.fill(new Rectangle(0, 0, getWidth(), getHeight())); + g2.setComposite(AlphaComposite.SrcOver); + g2.setPaint(View.getSwfBackgroundColor()); + g2.fill(new Rectangle(0, 0, getWidth(), getHeight())); + + g2.setComposite(AlphaComposite.SrcOver); + if (rect != null) { + g2.drawImage(img.getBufferedImage(), rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, 0, 0, img.getWidth(), img.getHeight(), null); + } + } finally { + if (g2 != null) { + g2.dispose(); + } + } + + } while (renderImage.contentsLost()); + } + + public IconPanel() { + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); + if (_img != null) { + calcRect(); + render(); + } + repaint(); + } + + }); + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + dragStart = e.getPoint(); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + dragStart = null; + } + } + + }); + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + if (dragStart != null && allowMove) { + Point dragEnd = e.getPoint(); + Point delta = new Point(dragEnd.x - dragStart.x, dragEnd.y - dragStart.y); + offsetPoint.x += delta.x; + offsetPoint.y += delta.y; + dragStart = dragEnd; + repaint(); + } + } + + }); + } + + public void setAutoFit(boolean autoFit) { + this.autoFit = autoFit; + repaint(); + } + + public synchronized BufferedImage getLastImage() { + if (_img == null) { + return null; + } + return _img.getBufferedImage(); + } + + public synchronized void setImg(SerializableImage img) { + this._img = img; + if (img != null) { + calcRect(); + render(); + } + repaint(); + } + + public synchronized Point toImagePoint(Point p) { + if (_img == null) { + return null; + } + + return new Point((p.x - _rect.x) * _img.getWidth() / _rect.width, (p.y - _rect.y) * _img.getHeight() / _rect.height); + } + + private void setAllowMove(boolean allowMove) { + this.allowMove = allowMove; + if (!allowMove) { + offsetPoint = new Point(); + } + } + + private synchronized void calcRect() { + if (_img != null) { + int w1 = (int) (_img.getWidth() * (lowQuality ? LQ_FACTOR : 1)); + int h1 = (int) (_img.getHeight() * (lowQuality ? LQ_FACTOR : 1)); + + int w2 = getWidth(); + int h2 = getHeight(); + + int w; + int h; + if (autoFit) { + if (w1 <= w2 && h1 <= h2) { + w = w1; + h = h1; + } else { + + h = h1 * w2 / w1; + if (h > h2) { + w = w1 * h2 / h1; + h = h2; + } else { + w = w2; + } + } + } else { + w = w1; + h = h1; + } + + setAllowMove(h > h2 || w > w2); + _rect = new Rectangle(getWidth() / 2 - w / 2 + offsetPoint.x, getHeight() / 2 - h / 2 + offsetPoint.y, w, h); + } else { + _rect = null; + } + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + + if (renderImage != null) { + calcRect(); + if (renderImage.validate(View.getDefaultConfiguration()) != VolatileImage.IMAGE_OK) { + renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); + render(); + } + + if (renderImage != null) { + g2d.drawImage(renderImage, 0, 0, null); + } + } + g2d.setColor(Color.red); + + DecimalFormat df = new DecimalFormat(); + df.setMaximumFractionDigits(2); + df.setMinimumFractionDigits(0); + df.setGroupingUsed(false); + + float frameLoss = 100 - (getFpsIs() / fpsShouldBe * 100); + + if (Configuration._debugMode.get()) { + g2d.drawString("frameLoss:" + df.format(frameLoss) + "%", 20, 20); + } + } + } + + @Override + public void setBackground(Color bg) { + if (iconPanel != null) { + iconPanel.setBackground(bg); + } + super.setBackground(bg); + } + + @Override + public synchronized void addMouseListener(MouseListener l) { + iconPanel.addMouseListener(l); + } + + @Override + public synchronized void removeMouseListener(MouseListener l) { + iconPanel.removeMouseListener(l); + } + + @Override + public synchronized void addMouseMotionListener(MouseMotionListener l) { + iconPanel.addMouseMotionListener(l); + } + + @Override + public synchronized void removeMouseMotionListener(MouseMotionListener l) { + iconPanel.removeMouseMotionListener(l); + } + + private void updatePos(Timelined timelined, MouseEvent lastMouseEvent, Timer thisTimer) { + if (timelined != null) { + + BoundedTag bounded = (BoundedTag) timelined; + RECT rect = bounded.getRect(); + int width = rect.getWidth(); + double scale = 1.0; + /*if (width > swf.displayRect.getWidth()) { + scale = (double) swf.displayRect.getWidth() / (double) width; + }*/ + Matrix m = Matrix.getTranslateInstance(-rect.Xmin, -rect.Ymin); + m.scale(scale); + + Point p = lastMouseEvent == null ? null : lastMouseEvent.getPoint(); + + synchronized (ImagePanel.class) { + if (timer == thisTimer) { + cursorPosition = p; + } + } + } + } + + private void showSelectedName() { + if (selectedDepth > -1 && frame > -1) { + DepthState ds = timelined.getTimeline().getFrame(frame).layers.get(selectedDepth); + if (ds != null) { + CharacterTag cht = timelined.getTimeline().swf.getCharacter(ds.characterId); + if (cht != null) { + debugLabel.setText(cht.getName()); + } + } + } + } + + public void hideMouseSelection() { + if (selectedDepth > -1) { + showSelectedName(); + } else { + debugLabel.setText(" - "); + } + } + + public ImagePanel() { + super(new BorderLayout()); + //iconPanel.setHorizontalAlignment(JLabel.CENTER); + setOpaque(true); + setBackground(View.getDefaultBackgroundColor()); + + loop = true; + iconPanel = new IconPanel(); + //labelPan.add(label, new GridBagConstraints()); + add(iconPanel, BorderLayout.CENTER); + add(debugLabel, BorderLayout.NORTH); + iconPanel.addMouseListener(new MouseAdapter() { + + @Override + public void mouseEntered(MouseEvent e) { + synchronized (ImagePanel.class) { + lastMouseEvent = e; + redraw(); + } + } + + @Override + public void mouseExited(MouseEvent e) { + synchronized (ImagePanel.class) { + lastMouseEvent = null; + hideMouseSelection(); + redraw(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + synchronized (ImagePanel.class) { + mouseButton = e.getButton(); + lastMouseEvent = e; + redraw(); + ButtonTag button = iconPanel.mouseOverButton; + if (button != null) { + DefineButtonSoundTag sounds = button.getSounds(); + if (sounds != null && sounds.buttonSoundChar2 != 0) { // OverUpToOverDown + playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar2), sounds.buttonSoundInfo2, timer); + } + } + } + } + + @Override + public void mouseReleased(MouseEvent e) { + synchronized (ImagePanel.class) { + mouseButton = 0; + lastMouseEvent = e; + redraw(); + ButtonTag button = iconPanel.mouseOverButton; + if (button != null) { + DefineButtonSoundTag sounds = button.getSounds(); + if (sounds != null && sounds.buttonSoundChar3 != 0) { // OverDownToOverUp + playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar3), sounds.buttonSoundInfo3, timer); + } + } + } + } + + }); + iconPanel.addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + synchronized (ImagePanel.class) { + lastMouseEvent = e; + redraw(); + } + } + + @Override + public void mouseDragged(MouseEvent e) { + synchronized (ImagePanel.class) { + lastMouseEvent = e; + redraw(); + } + } + + }); + } + + private synchronized void redraw() { + if (timer == null && timelined != null) { + startTimer(timelined.getTimeline(), false); + } + } + + @Override + public synchronized void zoom(Zoom zoom) { + boolean modified = this.zoom.value != zoom.value || this.zoom.fit != zoom.fit; + if (modified) { + this.zoom = zoom; + displayObjectCache.clear(); + redraw(); + if (textTag != null) { + setText(textTag, newTextTag); + } + + fireMediaDisplayStateChanged(); + } + } + + @Override + public synchronized BufferedImage printScreen() { + return iconPanel.getLastImage(); + } + + @Override + public synchronized double getZoomToFit() { + if (timelined != null) { + RECT bounds = timelined.getRect(); + double w1 = bounds.getWidth() / SWF.unitDivisor; + double h1 = bounds.getHeight() / SWF.unitDivisor; + + double w2 = getWidth(); + double h2 = getHeight(); + + double w; + double h; + h = h1 * w2 / w1; + if (h > h2) { + w = w1 * h2 / h1; + } else { + w = w2; + } + + if (w1 <= Double.MIN_NORMAL) { + return 1.0; + } + + return (double) w / (double) w1; + } + + return 1; + } + + @Override + public synchronized boolean zoomAvailable() { + return zoomAvailable; + } + + public void setTimelined(final Timelined drawable, final SWF swf, int frame) { + Stage stage = new Stage(drawable) { + @Override + public void callFrame(int frame) { + executeFrame(frame); + } + + @Override + public Object callFunction(long functionAddress, long functionLength, List args, Map regNames, Object thisObj) { + try { + SWFInputStream sis = new SWFInputStream(swf, swf.uncompressedData, functionAddress, (int) (functionAddress + functionLength)); + return execute(sis); + } catch (IOException ex) { + Logger.getLogger(ImagePanel.class.getName()).log(Level.SEVERE, null, ex); + } + return Undefined.INSTANCE; + } + + @Override + public int getCurrentFrame() { + return ImagePanel.this.getCurrentFrame(); + } + + @Override + public int getTotalFrames() { + return ImagePanel.this.getTotalFrames(); + } + + @Override + public void gotoFrame(int frame) { + ImagePanel.this.pause(); + ImagePanel.this.gotoFrame(frame); + } + + @Override + public void gotoLabel(String label) { + //TODO + } + + @Override + public void pause() { + ImagePanel.this.pause(); + } + + @Override + public void play() { + ImagePanel.this.play(); + } + + @Override + public void trace(Object... val) { + for (Object o : val) { + System.out.println("trace:" + o.toString()); + } + } + + }; + lda = new LocalDataArea(stage); + synchronized (ImagePanel.class) { + stopInternal(); + if (drawable instanceof ButtonTag) { + frame = ButtonTag.FRAME_UP; + } + + displayObjectCache.clear(); + this.timelined = drawable; + this.swf = swf; + zoomAvailable = true; + timer = null; + if (frame > -1) { + this.frame = frame; + this.stillFrame = true; + } else { + this.frame = 0; + this.stillFrame = false; + } + + loaded = true; + + if (drawable.getTimeline().getFrameCount() == 0) { + clearImagePanel(); + return; + } + + time = 0; + drawReady = false; + redraw(); + play(); + } + + synchronized (delayObject) { + try { + delayObject.wait(drawWaitLimit); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + + synchronized (ImagePanel.class) { + if (!drawReady) { + clearImagePanel(); + } + } + + fireMediaDisplayStateChanged(); + } + + public synchronized void setImage(SerializableImage image) { + lda = null; + setBackground(View.getSwfBackgroundColor()); + clear(); + + timelined = null; + loaded = true; + stillFrame = true; + zoomAvailable = false; + iconPanel.setImg(image); + drawReady = true; + + fireMediaDisplayStateChanged(); + } + + public synchronized void setText(TextTag textTag, TextTag newTextTag) { + setBackground(View.getSwfBackgroundColor()); + clear(); + + lda = null; + timelined = null; + loaded = true; + stillFrame = true; + zoomAvailable = true; + + this.textTag = textTag; + this.newTextTag = newTextTag; + + double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; + + RECT rect = textTag.getRect(); + int width = (int) (rect.getWidth() * zoomDouble); + int height = (int) (rect.getHeight() * zoomDouble); + SerializableImage image = new SerializableImage((int) (width / SWF.unitDivisor) + 1, + (int) (height / SWF.unitDivisor) + 1, SerializableImage.TYPE_INT_ARGB); + image.fillTransparent(); + Matrix m = Matrix.getTranslateInstance(-rect.Xmin * zoomDouble, -rect.Ymin * zoomDouble); + m.scale(zoomDouble); + textTag.toImage(0, 0, 0, new RenderContext(), image, false, m, m, m, new ConstantColorColorTransform(0xFFC0C0C0)); + + if (newTextTag != null) { + newTextTag.toImage(0, 0, 0, new RenderContext(), image, false, m, m, m, new ConstantColorColorTransform(0xFF000000)); + } + + iconPanel.setImg(image); + drawReady = true; + + fireMediaDisplayStateChanged(); + } + + private synchronized void clearImagePanel() { + iconPanel.setImg(null); + } + + @Override + public synchronized int getCurrentFrame() { + return frame; + } + + @Override + public synchronized int getTotalFrames() { + if (timelined == null) { + return 0; + } + if (stillFrame) { + return 0; + } + return timelined.getTimeline().getFrameCount(); + } + + @Override + public void pause() { + stopInternal(); + redraw(); + } + + @Override + public void stop() { + stopInternal(); + rewind(); + redraw(); + } + + @Override + public void close() throws IOException { + stopInternal(); + } + + private void stopAllSounds() { + for (int i = soundPlayers.size() - 1; i >= 0; i--) { + SoundTagPlayer pl = soundPlayers.get(i); + pl.close(); + } + soundPlayers.clear(); + } + + private void clear() { + if (timer != null) { + timer.cancel(); + timer = null; + fireMediaDisplayStateChanged(); + } + + textTag = null; + newTextTag = null; + displayObjectCache.clear(); + } + + private void nextFrame(Timer thisTimer, final int cnt, final int timeShouldBe) { + drawFrame(thisTimer, true); + + synchronized (ImagePanel.class) { + if (timelined != null && timer == thisTimer) { + int frameCount = timelined.getTimeline().getFrameCount(); + + int oldFrame = frame; + for (int i = 0; i < cnt; i++) { + if (!stillFrame && frameCount > 0) { + frame = (frame + 1) % frameCount; + } + + if (!stillFrame && frame == frameCount - 1 && !loop) { + stopInternal(); + return; + } + + if (i < cnt - 1) { //skip not displayed frames, do not display, only play sounds, etc. + drawFrame(thisTimer, false); + } + } + if (frame != oldFrame) { + if (frame == 0) { + stopAllSounds(); + } + time = 0; + } else { + time = timeShouldBe; + } + } + } + + fireMediaDisplayStateChanged(); + } + + private static SerializableImage getFrame(SWF swf, int frame, int time, Timelined drawable, RenderContext renderContext, int selectedDepth, double zoom) { + Timeline timeline = drawable.getTimeline(); + //int mouseButton = renderContext.mouseButton; + //Point cursorPosition = renderContext.cursorPosition; + //String key = "drawable_" + frame + "_" + drawable.hashCode() + "_" + mouseButton + "_depth" + selectedDepth + "_" + (cursorPosition == null ? "out" : cursorPosition.hashCode()) + "_" + zoom + "_" + timeline.fontFrameNum; + SerializableImage img; + //SerializableImage img = swf.getFromCache(key); + //if (img == null) { + //boolean shouldCache = timeline.isSingleFrame(frame); + RECT rect = drawable.getRect(); + + int width = (int) (rect.getWidth() * zoom); + int height = (int) (rect.getHeight() * zoom); + SerializableImage image = new SerializableImage((int) Math.ceil(width / SWF.unitDivisor), + (int) Math.ceil(height / SWF.unitDivisor), SerializableImage.TYPE_INT_ARGB); + //renderContext.borderImage = new SerializableImage(image.getWidth(), image.getHeight(), SerializableImage.TYPE_INT_ARGB); + image.fillTransparent(); + Matrix m = new Matrix(); + m.translate(-rect.Xmin * zoom, -rect.Ymin * zoom); + m.scale(zoom); + timeline.toImage(frame, time, renderContext, image, false, m, m, m, null); + + Graphics2D gg = (Graphics2D) image.getGraphics(); + gg.setStroke(new BasicStroke(3)); + gg.setPaint(Color.green); + gg.setTransform(AffineTransform.getTranslateInstance(0, 0)); + DepthState ds = null; + if (timeline.getFrameCount() > frame) { + ds = timeline.getFrame(frame).layers.get(selectedDepth); + } + + if (ds != null) { + CharacterTag cht = swf.getCharacter(ds.characterId); + if (cht != null) { + if (cht instanceof DrawableTag) { + DrawableTag dt = (DrawableTag) cht; + int drawableFrameCount = dt.getNumFrames(); + if (drawableFrameCount == 0) { + drawableFrameCount = 1; + } + + int dframe = time % drawableFrameCount; + Shape outline = dt.getOutline(dframe, time, ds.ratio, renderContext, Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m.concatenate(new Matrix(ds.matrix))), true); + Rectangle bounds = outline.getBounds(); + gg.setStroke(new BasicStroke(2.0f, + BasicStroke.CAP_BUTT, + BasicStroke.JOIN_MITER, + 10.0f, new float[]{10.0f}, 0.0f)); + gg.setPaint(Color.red); + gg.draw(bounds); + } + } + } + + img = image; + + /*if (shouldCache) { + swf.putToCache(key, img); + }*/ + //} + return img; + } + + private Object execute(SWFInputStream sis) throws IOException { + if (!Configuration.internalFlashViewerExecuteAs12.get()) { + return Undefined.INSTANCE; + } + if (lda == null) { + return Undefined.INSTANCE; + } + long ip = sis.getPos(); + //System.err.println("============="); + Action a; + while ((a = sis.readAction()) != null) { + int actionLengthWithHeader = a.getTotalActionLength(); + a.setAddress(ip); + a.execute(lda); + /*System.err.print("" + a + ", stack: ["); + for (Object o : lda.stack) { + System.err.print("" + o + ","); + } + System.err.println("]");*/ + if (lda.returnValue != null) { + return lda.returnValue; + } + if (lda.jump != null) { + ip = lda.jump; + lda.jump = null; + } else { + ip += actionLengthWithHeader; + } + sis.seek(ip); + } + return Undefined.INSTANCE; + } + + private void executeFrame(int frame) { + if (!Configuration.internalFlashViewerExecuteAs12.get()) { + return; + } + if (timelined == null) { + return; + } + Frame f = timelined.getTimeline().getFrame(frame); + List actions = f.actions; + if (lda != null) { + lda.clear(); + } + for (DoActionTag src : actions) { + try { + ByteArrayRange actionBytes = src.getActionBytes(); + int prevLength = actionBytes.getPos(); + SWFInputStream rri = new SWFInputStream(swf, actionBytes.getArray(), 0, prevLength + actionBytes.getLength()); + if (prevLength != 0) { + rri.seek(prevLength); + } + execute(rri); + } catch (IOException ex) { + Logger.getLogger(ImagePanel.class.getName()).log(Level.SEVERE, null, ex); + } + + } + } + + private void drawFrame(Timer thisTimer, boolean display) { + Timelined timelined; + MouseEvent lastMouseEvent; + int frame; + int time; + Point cursorPosition; + int mouseButton; + int selectedDepth; + Zoom zoom; + SWF swf; + + synchronized (ImagePanel.class) { + timelined = this.timelined; + lastMouseEvent = this.lastMouseEvent; + } + + synchronized (ImagePanel.class) { + frame = this.frame; + time = this.time; + cursorPosition = this.cursorPosition; + if (cursorPosition != null) { + cursorPosition = iconPanel.toImagePoint(cursorPosition); + } + + mouseButton = this.mouseButton; + selectedDepth = this.selectedDepth; + zoom = this.zoom; + swf = this.swf; + } + + if (timelined == null) { + return; + } + + RenderContext renderContext = new RenderContext(); + renderContext.displayObjectCache = displayObjectCache; + if (cursorPosition != null) { + renderContext.cursorPosition = new Point((int) (cursorPosition.x * SWF.unitDivisor), (int) (cursorPosition.y * SWF.unitDivisor)); + } + + renderContext.mouseButton = mouseButton; + renderContext.stateUnderCursor = new ArrayList<>(); + + SerializableImage img; + try { + Timeline timeline = timelined.getTimeline(); + if (frame >= timeline.getFrameCount()) { + return; + } + + double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; + if (lowQuality) { + zoomDouble /= LQ_FACTOR; + } + updatePos(timelined, lastMouseEvent, thisTimer); + + Matrix mat = new Matrix(); + mat.translateX = swf.displayRect.Xmin; + mat.translateY = swf.displayRect.Ymin; + + 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; + } + } + + List sounds = new ArrayList<>(); + List soundClasses = new ArrayList<>(); + timeline.getSounds(frame, time, renderContext.mouseOverButton, mouseButton, sounds, soundClasses); + for (int cid : swf.getCharacters().keySet()) { + CharacterTag c = swf.getCharacter(cid); + for (String cls : soundClasses) { + if (cls.equals(c.getClassName())) { + sounds.add(cid); + } + } + } + + for (int sndId : sounds) { + CharacterTag c = swf.getCharacter(sndId); + if (c instanceof SoundTag) { + SoundTag st = (SoundTag) c; + playSound(st, null, thisTimer); + } + } + executeFrame(frame); + } catch (Throwable ex) { + // swf was closed during the rendering probably + return; + } + if (display) { + + StringBuilder ret = new StringBuilder(); + + if (cursorPosition != null) { + ret.append(" [").append(cursorPosition.x).append(",").append(cursorPosition.y).append("] : "); + } + + boolean handCursor = renderContext.mouseOverButton != null; + boolean first = true; + for (int i = renderContext.stateUnderCursor.size() - 1; i >= 0; i--) { + DepthState ds = renderContext.stateUnderCursor.get(i); + if (!first) { + ret.append(", "); + } + + first = false; + CharacterTag c = swf.getCharacter(ds.characterId); + ret.append(c.toString()); + } + + if (first) { + ret.append(" - "); + } + + ButtonTag lastMouseOverButton; + synchronized (ImagePanel.class) { + if (timer == thisTimer) { + iconPanel.setImg(img); + lastMouseOverButton = iconPanel.mouseOverButton; + iconPanel.mouseOverButton = renderContext.mouseOverButton; + debugLabel.setText(ret.toString()); + if (handCursor) { + iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } else if (iconPanel.hasAllowMove()) { + iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); + } else { + iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + if (lastMouseOverButton != renderContext.mouseOverButton) { + ButtonTag b = renderContext.mouseOverButton; + if (b != null) { + // New mouse entered + DefineButtonSoundTag sounds = b.getSounds(); + if (sounds != null && sounds.buttonSoundChar1 != 0) { // IdleToOverUp + playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar1), sounds.buttonSoundInfo1, timer); + } + } + + b = lastMouseOverButton; + if (b != null) { + // Old mouse leave + DefineButtonSoundTag sounds = b.getSounds(); + if (sounds != null && sounds.buttonSoundChar0 != 0) { // OverUpToIdle + playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar0), sounds.buttonSoundInfo0, timer); + } + } + } + + drawReady = true; + synchronized (delayObject) { + delayObject.notify(); + } + } + } + } + } + + private void playSound(SoundTag st, SOUNDINFO soundInfo, Timer thisTimer) { + final SoundTagPlayer sp; + try { + int loopCount = 1; + if (soundInfo != null && soundInfo.hasLoops) { + loopCount = Math.max(1, soundInfo.loopCount); + } + + sp = new SoundTagPlayer(st, loopCount, false); + sp.addEventListener(new MediaDisplayListener() { + + @Override + public void mediaDisplayStateChanged(MediaDisplay source) { + } + + @Override + public void playingFinished(MediaDisplay source) { + synchronized (ImagePanel.class) { + sp.close(); + soundPlayers.remove(sp); + } + } + }); + + synchronized (ImagePanel.class) { + if (timer != null && timer == thisTimer) { + soundPlayers.add(sp); + sp.play(); + } else { + sp.close(); + } + } + } catch (LineUnavailableException | IOException | UnsupportedAudioFileException ex) { + logger.log(Level.SEVERE, "Error during playing sound", ex); + } + } + + public synchronized void clearAll() { + stopInternal(); + clearImagePanel(); + timelined = null; + swf = null; + + fireMediaDisplayStateChanged(); + } + + private synchronized void stopInternal() { + clear(); + stopAllSounds(); + } + + @Override + public synchronized void play() { + stopInternal(); + if (timelined != null) { + Timeline timeline = timelined.getTimeline(); + if (!stillFrame && frame == timeline.getFrameCount() - 1) { + frame = 0; + } + + startTimer(timeline, true); + } + } + + private synchronized void setMsPerFrame(int val) { + this.msPerFrame = val; + } + + private synchronized int getMsPerFrame() { + return this.msPerFrame; + } + + private long startRun = 0L; + + private final long startDrop = 0L; + + private int skippedFrames = 0; + + private float fpsShouldBe = 0; + + private float fpsIs = 0; + + private Timer fpsTimer; + + private int startFrame = 0; + + private synchronized void setFpsIs(float val) { + fpsIs = val; + } + + private synchronized float getFpsIs() { + return fpsIs; + } + + private synchronized void setSkippedFrames(int val) { + skippedFrames = val; + } + + private synchronized void addSkippedFrames(int val) { + skippedFrames += val; + } + + private synchronized int getSkippedFrames() { + return skippedFrames; + } + + private synchronized int getAndResetSkippedFrames() { + int ret = skippedFrames; + skippedFrames = 0; + return ret; + } + + private void scheduleTask(boolean singleFrame, long msDelay) { + TimerTask task = new TimerTask() { + public final Timer thisTimer = timer; + + public final boolean isSingleFrame = singleFrame; + + private long lastRun = 0L; + + @Override + public void run() { + try { + synchronized (ImagePanel.class) { + if (timer != thisTimer) { + return; + } + } + lastRun = System.currentTimeMillis(); + int curFrame = frame; + long delay = getMsPerFrame(); + if (isSingleFrame) { + drawFrame(thisTimer, true); + synchronized (ImagePanel.class) { + thisTimer.cancel(); + if (timer == thisTimer) { + timer = null; + } + } + + fireMediaDisplayStateChanged(); + } else { + //Time before drawing current frame + long frameTimeMsIs = System.currentTimeMillis(); + //Total number of frames in this timeline + int frameCount = timelined.getTimeline().getFrameCount(); + //How many ticks (= times where frame should be displayed in framerate) are there from hitting play button + int ticksFromStart = (int) Math.floor((frameTimeMsIs - startRun) / (double) getMsPerFrame()) + 1; + //Add ticks to first frame when hitting play button, ignoring total framecount => this value can be larger than number of frames in timeline + int frameOverMaxShouldBeNow = startFrame + ticksFromStart; + //Apply maximum frames repating, this is actual frame which should be drawed now + int frameShouldBeNow = frameOverMaxShouldBeNow % frameCount; + + //How many frames are there between last displayed frame and now. For perfect display(=no framedrop), value should be 1 + int skipFrames = frameShouldBeNow - curFrame; + //It is negative for some reason, this will display older frames. Add frameCount to stay in modulu framecount. + if (skipFrames < 0) { + skipFrames += frameCount; + } + //Change for more than 1 frame + if (skipFrames > 1) { + addSkippedFrames(skipFrames - 1); //drop those frames, draw only last one + } + //Frame "time" - ticks in current frame + int currentFrameTicks = 0; + if (frameCount == 1) { //We have only one frame, so the ticks on that frame equal ticks on whole timeline + currentFrameTicks = ticksFromStart; + } + nextFrame(thisTimer, skipFrames, currentFrameTicks); + + long afterDrawFrameTimeMsIs = System.currentTimeMillis(); + + int nextFrameOverMax = frameOverMaxShouldBeNow; + while (delay < 0) { //while the frame time already passed + nextFrameOverMax++; + long nextFrameOverMaxTimeMsShouldBe = startRun + getMsPerFrame() * nextFrameOverMax; + delay = nextFrameOverMaxTimeMsShouldBe - afterDrawFrameTimeMsIs; + } + } + //schedule next run of the task + scheduleTask(isSingleFrame, delay); + + } catch (Exception ex) { + logger.log(Level.SEVERE, "Frame drawing error", ex); + } + } + }; + if (timer != null) { + timer.schedule(task, msDelay); + } + } + + private synchronized void startTimer(Timeline timeline, boolean playing) { + + startRun = System.currentTimeMillis(); + startFrame = frame; + float frameRate = timeline.frameRate; + setMsPerFrame(frameRate == 0 ? 1000 : (int) (1000.0 / frameRate)); + final boolean singleFrame = !playing + || (stillFrame && timeline.isSingleFrame(frame)) + || (!stillFrame && timeline.getRealFrameCount() <= 1 && timeline.isSingleFrame()); + + if (fpsTimer == null) { + fpsTimer = new Timer(); + fpsTimer.schedule(new TimerTask() { + @Override + public void run() { + float skipped = getAndResetSkippedFrames(); + setFpsIs(fpsShouldBe - skipped); + } + }, 1000, 1000); + } + timer = new Timer(); + fpsShouldBe = timeline.frameRate; + fpsIs = fpsShouldBe; + scheduleTask(singleFrame, 0); + + } + + @Override + public synchronized void rewind() { + frame = 0; + fireMediaDisplayStateChanged(); + } + + @Override + public synchronized boolean isPlaying() { + if (timelined == null || stillFrame) { + return false; + } + + return (timelined.getTimeline().getFrameCount() <= 1) || (timer != null); + } + + @Override + public void setLoop(boolean loop) { + this.loop = loop; + } + + @Override + public synchronized void gotoFrame(int frame) { + if (timelined == null) { + return; + } + Timeline timeline = timelined.getTimeline(); + if (frame >= timeline.getFrameCount()) { + return; + } + if (frame < 0) { + return; + } + + this.frame = frame; + stopInternal(); + redraw(); + fireMediaDisplayStateChanged(); + } + + @Override + public synchronized float getFrameRate() { + if (timelined == null) { + return 1; + } + if (stillFrame) { + return 1; + } + return timelined.getTimeline().frameRate; + } + + @Override + public synchronized boolean isLoaded() { + return loaded; + } + + @Override + public boolean loopAvailable() { + return false; + } + + @Override + public boolean screenAvailable() { + return true; + } + + @Override + public synchronized Zoom getZoom() { + return zoom; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index 93efd3cff..28a3f9d6d 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -1,1303 +1,1306 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui; - -import com.jpexs.debugger.flash.DebuggerCommands; -import com.jpexs.decompiler.flash.ApplicationInfo; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFBundle; -import com.jpexs.decompiler.flash.SWFSourceInfo; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.configuration.ConfigurationItemChangeListener; -import com.jpexs.decompiler.flash.console.ContextMenuTools; -import com.jpexs.decompiler.flash.gui.debugger.DebuggerTools; -import com.jpexs.decompiler.flash.gui.helpers.CheckResources; -import com.jpexs.decompiler.flash.tags.ABCContainerTag; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.utf8.Utf8Helper; -import com.sun.jna.Platform; -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.KeyEventDispatcher; -import java.awt.KeyboardFocusManager; -import java.awt.ScrollPane; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.awt.event.WindowEvent; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.AbstractButton; -import javax.swing.JComboBox; -import javax.swing.JDialog; -import javax.swing.JEditorPane; -import javax.swing.JFrame; -import javax.swing.JOptionPane; - -/** - * - * @author JPEXS - */ -public abstract class MainFrameMenu implements MenuBuilder { - - private final MainFrame mainFrame; - - private KeyEventDispatcher keyEventDispatcher; - - private SWF swf; - - private ConfigurationItemChangeListener configListenerAutoDeobfuscate; - - private ConfigurationItemChangeListener configListenerSimplifyExpressions; - - private ConfigurationItemChangeListener configListenerInternalFlashViewer; - - private ConfigurationItemChangeListener configListenerParallelSpeedUp; - - private ConfigurationItemChangeListener configListenerDecompile; - - //private ConfigurationItemChangeListener configListenerCacheOnDisk; - private ConfigurationItemChangeListener configListenerGotoMainClassOnStartup; - - private ConfigurationItemChangeListener configListenerAutoRenameIdentifiers; - - private ConfigurationItemChangeListener configListenerAutoOpenLoadedSWFs; - - protected final Map menuHotkeys = new HashMap<>(); - - @Override - public HotKey getMenuHotkey(String path) { - return menuHotkeys.get(path); - } - - protected final Map menuActions = new HashMap<>(); - - public boolean isInternalFlashViewerSelected() { - return isMenuChecked("/settings/internalViewer"); //miInternalViewer.isSelected(); - } - - private final boolean externalFlashPlayerUnavailable; - - public MainFrameMenu(MainFrame mainFrame, boolean externalFlashPlayerUnavailable) { - registerHotKeys(); - this.mainFrame = mainFrame; - this.externalFlashPlayerUnavailable = externalFlashPlayerUnavailable; - } - - protected String translate(String key) { - return mainFrame.translate(key); - } - - protected boolean openActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return false; - } - - Main.openFileDialog(); - return true; - } - - protected boolean saveActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return false; - } - - if (swf != null) { - boolean saved = false; - if (swf.swfList != null && swf.swfList.isBundle()) { - SWFBundle bundle = swf.swfList.bundle; - if (!bundle.isReadOnly()) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - swf.saveTo(baos); - saved = bundle.putSWF(swf.getFileTitle(), new ByteArrayInputStream(baos.toByteArray())); - } catch (IOException ex) { - Logger.getLogger(MainFrameMenu.class.getName()).log(Level.SEVERE, "Cannot save SWF", ex); - } - } - } else if (swf.binaryData != null) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - swf.saveTo(baos); - swf.binaryData.binaryData = new ByteArrayRange(baos.toByteArray()); - swf.binaryData.setModified(true); - saved = true; - } catch (IOException ex) { - Logger.getLogger(MainFrameMenu.class.getName()).log(Level.SEVERE, "Cannot save SWF", ex); - } - } else if (swf.getFile() == null) { - saved = saveAs(swf, SaveFileMode.SAVEAS); - } else { - try { - Main.saveFile(swf, swf.getFile()); - saved = true; - } catch (IOException ex) { - Logger.getLogger(MainFrameMenu.class.getName()).log(Level.SEVERE, null, ex); - View.showMessageDialog(null, translate("error.file.save"), translate("error"), JOptionPane.ERROR_MESSAGE); - } - } - if (saved) { - swf.clearModified(); - mainFrame.getPanel().refreshTree(swf); - } - - return true; - } - - return false; - } - - protected boolean saveAsActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return false; - } - - if (swf != null) { - if (saveAs(swf, SaveFileMode.SAVEAS)) { - swf.clearModified(); - } - - return true; - } - - return false; - } - - private boolean saveAs(SWF swf, SaveFileMode mode) { - if (Main.saveFileDialog(swf, mode)) { - mainFrame.setTitle(ApplicationInfo.applicationVerName + (Configuration.displayFileName.get() ? " - " + swf.getFileTitle() : "")); - updateComponents(swf); - return true; - } - return false; - } - - protected void saveAsExeActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - if (swf != null) { - saveAs(swf, SaveFileMode.EXE); - } - } - - protected void closeActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - if (swf == null) { - return; - } - - Main.closeFile(swf.swfList); - } - - protected boolean closeAllActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return false; - } - - if (swf != null) { - return Main.closeAll(); - } - - return false; - } - - protected void importTextActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - mainFrame.getPanel().importText(swf); - } - - protected void importScriptActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - mainFrame.getPanel().importScript(swf); - } - - protected void importSymbolClassActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - mainFrame.getPanel().importSymbolClass(swf); - } - - protected boolean exportAllActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return false; - } - - return export(false); - } - - protected boolean exportSelectedActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return false; - } - - return export(true); - } - - protected boolean export(boolean onlySelected) { - if (swf != null) { - mainFrame.getPanel().export(onlySelected); - return true; - } - - return false; - } - - protected void exportFlaActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - mainFrame.getPanel().exportFla(swf); - } - - protected void importXmlActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - mainFrame.getPanel().importSwfXml(); - } - - protected void exportXmlActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - mainFrame.getPanel().exportSwfXml(); - } - - protected boolean searchActionPerformed(ActionEvent evt) { - return search(evt, null); - } - - protected boolean searchInTextPerformed(ActionEvent evt) { - return search(evt, true); - } - - protected boolean searchInActionPerformed(ActionEvent evt) { - return search(evt, false); - } - - protected boolean search(ActionEvent evt, Boolean searchInText) { - if (swf != null) { - mainFrame.getPanel().searchInActionScriptOrText(searchInText, swf); - return true; - } - - return false; - } - - protected boolean replaceActionPerformed(ActionEvent evt) { - if (swf != null) { - mainFrame.getPanel().replaceText(); - return true; - } - - return false; - } - - protected void showProxyActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - Main.showProxy(); - } - - protected boolean clearLog(ActionEvent evt) { - ErrorLogFrame.getInstance().clearLog(); - return true; - } - - protected void renameOneIdentifier(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - mainFrame.getPanel().renameOneIdentifier(swf); - } - - protected void renameInvalidIdentifiers(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - mainFrame.getPanel().renameIdentifiers(swf); - } - - protected void deobfuscationActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - mainFrame.getPanel().deobfuscate(); - } - - protected void setSubLimiter(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - AbstractButton button = (AbstractButton) evt.getSource(); - boolean selected = button.isSelected(); - Main.setSubLimiter(selected); - } - - protected void switchDebugger() { - DebuggerTools.switchDebugger(swf); - } - - protected void debuggerShowLogActionPerformed(ActionEvent evt) { - DebuggerTools.debuggerShowLog(); - } - - protected void debuggerInjectLoader(ActionEvent evt) { - DebuggerTools.injectDebugLoader(swf); - refreshDecompiled(); - } - - protected void debuggerReplaceTraceCallsActionPerformed(ActionEvent evt) { - ReplaceTraceDialog rtd = new ReplaceTraceDialog(Configuration.lastDebuggerReplaceFunction.get()); - rtd.setVisible(true); - if (rtd.getValue() != null) { - String fname = rtd.getValue(); - DebuggerTools.replaceTraceCalls(swf, fname); - mainFrame.getPanel().refreshDecompiled(); - Configuration.lastDebuggerReplaceFunction.set(rtd.getValue()); - } - } - - protected void clearRecentFilesActionPerformed(ActionEvent evt) { - Configuration.recentFiles.set(null); - } - - protected void removeNonScripts() { - mainFrame.getPanel().removeNonScripts(swf); - } - - protected void removeExceptSelected() { - mainFrame.getPanel().removeExceptSelected(swf); - } - - protected void refreshDecompiled() { - mainFrame.getPanel().refreshDecompiled(); - } - - protected boolean previousTag(ActionEvent evt) { - return mainFrame.getPanel().previousTag(); - } - - protected boolean nextTag(ActionEvent evt) { - return mainFrame.getPanel().nextTag(); - } - - protected void checkResources() { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - PrintStream stream = new PrintStream(os); - 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)); - Container cnt = getContentPane(); - cnt.setLayout(new BorderLayout()); - String[] languages = SelectLanguageDialog.getAvailableLanguages().clone(); - languages[0] = "all"; - JComboBox languagesComboBox = new JComboBox<>(languages); - this.add(languagesComboBox, BorderLayout.NORTH); - ScrollPane scrollPane = new ScrollPane(); - JEditorPane editor = new JEditorPane(); - editor.setEditable(false); - editor.setText(str); - scrollPane.add(editor); - this.add(scrollPane, BorderLayout.CENTER); - this.setModal(true); - View.centerScreen(this); - languagesComboBox.addActionListener((ActionEvent e) -> { - String lang = (String) languagesComboBox.getSelectedItem(); - if (lang.equals("all")) { - lang = null; - } - ByteArrayOutputStream os = new ByteArrayOutputStream(); - try (PrintStream stream = new PrintStream(os, false, "UTF-8")) { - CheckResources.checkResources(stream, lang); - String str = new String(os.toByteArray(), Utf8Helper.charset); - editor.setText(str); - } catch (UnsupportedEncodingException ex) { - // ignore - } - }); - super.setVisible(bln); - } - }; - dialog.setVisible(true); - } - - protected void checkUpdatesActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - if (!Main.checkForUpdates()) { - View.showMessageDialog(null, translate("update.check.nonewversion"), translate("update.check.title"), JOptionPane.INFORMATION_MESSAGE); - } - } - - protected void helpUsActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - String helpUsURL = ApplicationInfo.PROJECT_PAGE + "/help_us.html?utm_source=app&utm_medium=menu&utm_campaign=app"; - if (!View.navigateUrl(helpUsURL)) { - View.showMessageDialog(null, translate("message.helpus").replace("%url%", helpUsURL)); - } - } - - protected void homePageActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - String homePageURL = ApplicationInfo.PROJECT_PAGE + "?utm_source=app&utm_medium=menu&utm_campaign=app"; - if (!View.navigateUrl(homePageURL)) { - View.showMessageDialog(null, translate("message.homepage").replace("%url%", homePageURL)); - } - } - - protected void aboutActionPerformed(ActionEvent evt) { - if (Main.isWorking()) { - return; - } - - Main.about(); - } - - protected boolean reloadActionPerformed(ActionEvent evt) { - if (swf != null) { - if (View.showConfirmDialog(null, translate("message.confirm.reload"), translate("message.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) { - Main.reloadFile(swf.swfList); - } - } - return true; - } - - protected boolean reloadAllActionPerformed(ActionEvent evt) { - if (swf != null) { - if (View.showConfirmDialog(null, translate("message.confirm.reloadAll"), translate("message.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) { - Main.reloadApp(); - } - - return true; - } - - Main.reloadApp(); - return true; - } - - protected void advancedSettingsActionPerformed(ActionEvent evt) { - Main.advancedSettings(); - } - - protected void searchMemoryActionPerformed(ActionEvent evt) { - Main.loadFromMemory(); - } - - protected void searchCacheActionPerformed(ActionEvent evt) { - Main.loadFromCache(); - } - - protected void gotoDucumentClassOnStartupActionPerformed(ActionEvent evt) { - AbstractButton button = (AbstractButton) evt.getSource(); - boolean selected = button.isSelected(); - - Configuration.gotoMainClassOnStartup.set(selected); - } - - protected void autoOpenLoadedSWFsActionPerformed(ActionEvent evt) { - AbstractButton button = (AbstractButton) evt.getSource(); - boolean selected = button.isSelected(); - - Configuration.autoOpenLoadedSWFs.set(selected); - } - - protected void autoRenameIdentifiersActionPerformed(ActionEvent evt) { - AbstractButton button = (AbstractButton) evt.getSource(); - boolean selected = button.isSelected(); - - Configuration.autoRenameIdentifiers.set(selected); - } - - /*protected void cacheOnDiskActionPerformed(ActionEvent evt) { - AbstractButton button = (AbstractButton) evt.getSource(); - boolean selected = button.isSelected(); - - Configuration.cacheOnDisk.set(selected); - if (selected) { - Cache.setStorageType(Cache.STORAGE_FILES); - } else { - Cache.setStorageType(Cache.STORAGE_MEMORY); - } - }*/ - protected void setLanguageActionPerformed(ActionEvent evt) { - new SelectLanguageDialog().display(); - } - - protected void disableDecompilationActionPerformed(ActionEvent evt) { - AbstractButton button = (AbstractButton) evt.getSource(); - boolean selected = button.isSelected(); - - Configuration.decompile.set(!selected); - mainFrame.getPanel().disableDecompilationChanged(); - } - - protected void associateActionPerformed(ActionEvent evt) { - AbstractButton button = (AbstractButton) evt.getSource(); - boolean selected = button.isSelected(); - - if (selected == ContextMenuTools.isAddedToContextMenu()) { - return; - } - ContextMenuTools.addToContextMenu(selected, false); - - // Update checkbox menuitem accordingly (User can cancel rights elevation) - new Timer().schedule(new TimerTask() { - @Override - public void run() { - button.setSelected(ContextMenuTools.isAddedToContextMenu()); - } - }, 1000); // It takes some time registry change to apply - } - - protected void gotoDucumentClassActionPerformed(ActionEvent evt) { - mainFrame.getPanel().gotoDocumentClass(mainFrame.getPanel().getCurrentSwf()); - } - - protected void parallelSpeedUpActionPerformed(ActionEvent evt) { - AbstractButton button = (AbstractButton) evt.getSource(); - boolean selected = button.isSelected(); - - String confStr = translate("message.confirm.parallel") + "\r\n"; - if (selected) { - confStr += " " + translate("message.confirm.on"); - } else { - confStr += " " + translate("message.confirm.off"); - } - if (View.showConfirmDialog(null, confStr, translate("message.parallel"), JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) { - Configuration.parallelSpeedUp.set(selected); - } else { - button.setSelected(Configuration.parallelSpeedUp.get()); - } - } - - protected void internalViewerSwitchActionPerformed(ActionEvent evt) { - AbstractButton button = (AbstractButton) evt.getSource(); - boolean selected = button.isSelected(); - - Configuration.internalFlashViewer.set(selected); - mainFrame.getPanel().reload(true); - } - - protected void simplifyExpressionsActionPerformed(ActionEvent evt) { - AbstractButton button = (AbstractButton) evt.getSource(); - boolean selected = button.isSelected(); - - Configuration.simplifyExpressions.set(selected); - mainFrame.getPanel().autoDeobfuscateChanged(); - } - - protected void autoDeobfuscationActionPerformed(ActionEvent evt) { - AbstractButton button = (AbstractButton) evt.getSource(); - boolean selected = button.isSelected(); - - if (View.showConfirmDialog(mainFrame.getPanel(), translate("message.confirm.autodeobfuscate") + "\r\n" + (selected ? translate("message.confirm.on") : translate("message.confirm.off")), translate("message.confirm"), JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) { - Configuration.autoDeobfuscate.set(selected); - mainFrame.getPanel().autoDeobfuscateChanged(); - } else { - button.setSelected(Configuration.autoDeobfuscate.get()); - } - } - - /*protected void deobfuscationMode(ActionEvent evt, int mode) { - Configuration.deobfuscationMode.set(mode); - mainFrame.getPanel().autoDeobfuscateChanged(); - }*/ - protected void exitActionPerformed(ActionEvent evt) { - JFrame frame = (JFrame) mainFrame; - frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING)); - } - - public void updateComponents() { - updateComponents(swf); - } - - public void updateComponents(SWF swf) { - this.swf = swf; - boolean isRunning = Main.isRunning(); - boolean isDebugRunning = Main.isDebugRunning(); - boolean isDebugPaused = Main.isDebugPaused(); - - boolean isRunningOrDebugging = isRunning || isDebugRunning; - - boolean swfSelected = swf != null; - boolean isWorking = Main.isWorking(); - List abcList = swf != null ? swf.getAbcList() : null; - boolean hasAbc = swfSelected && abcList != null && !abcList.isEmpty(); - boolean hasDebugger = hasAbc && DebuggerTools.hasDebugger(swf); - MainPanel mainPanel = mainFrame.getPanel(); - boolean swfLoaded = mainPanel != null ? !mainPanel.getSwfs().isEmpty() : false; - - setMenuEnabled("_/open", !isWorking); - setMenuEnabled("/file/open", !isWorking); - setMenuEnabled("_/save", swfSelected && !isWorking); - setMenuEnabled("/file/save", swfSelected && !isWorking); - setMenuEnabled("_/saveAs", swfSelected && !isWorking); - setMenuEnabled("/file/saveAs", swfSelected && !isWorking); - setMenuEnabled("/file/saveAsExe", swfSelected && !isWorking); - setMenuEnabled("_/close", swfSelected && !isWorking); - setMenuEnabled("/file/close", swfSelected && !isWorking); - setMenuEnabled("_/closeAll", swfLoaded && !isWorking); - setMenuEnabled("/file/closeAll", swfLoaded && !isWorking); - - setMenuEnabled("/file/export", swfSelected); - setMenuEnabled("_/exportAll", swfSelected && !isWorking); - setMenuEnabled("/file/export/exportAll", swfSelected && !isWorking); - setMenuEnabled("_/exportFla", swfSelected && !isWorking); - setMenuEnabled("/file/export/exportFla", swfSelected && !isWorking); - setMenuEnabled("_/exportSelected", swfSelected && !isWorking); - setMenuEnabled("/file/export/exportSelected", swfSelected && !isWorking); - setMenuEnabled("/file/export/exportXml", swfSelected && !isWorking); - - setMenuEnabled("/file/import", swfSelected); - setMenuEnabled("/file/import/importText", swfSelected && !isWorking); - setMenuEnabled("/file/import/importScript", swfSelected && !isWorking); - setMenuEnabled("/file/import/importSymbolClass", swfSelected && !isWorking); - setMenuEnabled("/file/import/importXml", swfSelected && !isWorking); - - setMenuEnabled("/tools/deobfuscation", swfSelected); - setMenuEnabled("/tools/deobfuscation/renameOneIdentifier", swfSelected && !isWorking); - setMenuEnabled("/tools/deobfuscation/renameInvalidIdentifiers", swfSelected && !isWorking); - setMenuEnabled("/tools/deobfuscation/deobfuscation", hasAbc); - - setMenuEnabled("/tools/search", swfSelected); - setMenuEnabled("/tools/replace", swfSelected); - setMenuEnabled("/tools/timeline", swfSelected); - setMenuEnabled("/tools/showProxy", !isWorking); - - setMenuEnabled("/tools/gotoDocumentClass", hasAbc); - /*setMenuEnabled("/tools/debugger/debuggerSwitch", hasAbc); - setMenuChecked("/tools/debugger/debuggerSwitch", hasDebugger); - setMenuEnabled("/tools/debugger/debuggerReplaceTrace", hasAbc && hasDebugger);*/ - //setMenuEnabled("/tools/debugger/debuggerInjectLoader", hasAbc && hasDebugger); - - setMenuEnabled("_/checkUpdates", !isWorking); - setMenuEnabled("/help/checkUpdates", !isWorking); - setMenuEnabled("/help/helpUs", !isWorking); - setMenuEnabled("/help/homePage", !isWorking); - setMenuEnabled("_/about", !isWorking); - setMenuEnabled("/help/about", !isWorking); - - setMenuEnabled("/file/start/run", swfSelected && !isRunningOrDebugging); - setMenuEnabled("/file/start/debug", swfSelected && !isRunningOrDebugging); - setMenuEnabled("/file/start/debugpcode", swfSelected && !isRunningOrDebugging); - - setMenuEnabled("/file/start/stop", isRunningOrDebugging); - setMenuEnabled("/debugging/debug/stop", isRunningOrDebugging); //same as previous - - setPathVisible("/debugging", isDebugRunning); - setMenuEnabled("/debugging/debug", isDebugRunning); - //setMenuEnabled("/debugging/debug/pause", isDebugRunning); - setMenuEnabled("/debugging/debug/stepOver", isDebugPaused); - setMenuEnabled("/debugging/debug/stepInto", isDebugPaused); - setMenuEnabled("/debugging/debug/stepOut", isDebugPaused); - setMenuEnabled("/debugging/debug/continue", isDebugPaused); - //setMenuEnabled("/debugging/debug/stack", isDebugPaused); - //setMenuEnabled("/debugging/debug/watch", isDebugPaused); - - } - - private void registerHotKeys() { - - KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - manager.addKeyEventDispatcher(keyEventDispatcher = this::dispatchKeyEvent); - } - - public void createMenuBar() { - initMenu(); - - if (supportsAppMenu()) { - addMenuItem("_", null, null, null, 0, null, false, null, false); - addMenuItem("_/open", translate("menu.file.open"), "open32", this::openActionPerformed, PRIORITY_TOP, this::loadRecent, false, null, false); - addMenuItem("_/save", translate("menu.file.save"), "save32", this::saveActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("_/saveAs", translate("menu.file.saveas"), "saveas32", this::saveAsActionPerformed, PRIORITY_TOP, null, true, null, false); - addSeparator("_"); - addMenuItem("_/exportFla", translate("menu.file.export.fla"), "exportfla32", this::exportFlaActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("_/exportAll", translate("menu.file.export.all"), "export32", this::exportAllActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("_/exportSelected", translate("menu.file.export.selection"), "exportsel32", this::exportSelectedActionPerformed, PRIORITY_TOP, null, true, null, false); - addSeparator("_"); - addMenuItem("_/checkUpdates", translate("menu.help.checkupdates"), "update32", this::checkUpdatesActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("_/about", translate("menu.help.about"), "about32", this::aboutActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("_/close", translate("menu.file.close"), "close32", this::closeActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("_/closeAll", translate("menu.file.closeAll"), "closeall32", this::closeAllActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("_/$exit", translate("menu.file.exit"), "exit32", this::exitActionPerformed, PRIORITY_TOP, null, true, null, false); - finishMenu("_"); - } - - addMenuItem("/file", translate("menu.file"), null, null, 0, null, false, null, false); - addMenuItem("/file/open", translate("menu.file.open"), "open32", this::openActionPerformed, PRIORITY_TOP, this::loadRecent, !supportsMenuAction(), new HotKey("CTRL+SHIFT+O"), false); - - if (!supportsMenuAction()) { - addMenuItem("/file/recent", translate("menu.recentFiles"), null, null, 0, this::loadRecent, false, null, false); - finishMenu("/file/recent"); - } else { - finishMenu("/file/open"); - } - - addMenuItem("/file/save", translate("menu.file.save"), "save32", this::saveActionPerformed, PRIORITY_TOP, null, true, new HotKey("CTRL+SHIFT+S"), false); - addMenuItem("/file/saveAs", translate("menu.file.saveas"), "saveas16", this::saveAsActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+A"), false); - addMenuItem("/file/saveAsExe", translate("menu.file.saveasexe"), "saveasexe16", this::saveAsExeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/file/reload", translate("menu.file.reload"), "reload16", this::reloadActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+R"), false); - addMenuItem("/file/reloadAll", translate("menu.file.reloadAll"), "reload16", this::reloadAllActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - - addSeparator("/file"); - - addMenuItem("/file/export", translate("menu.export"), null, null, 0, null, false, null, false); - addMenuItem("/file/export/exportFla", translate("menu.file.export.fla"), "exportfla32", this::exportFlaActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("/file/export/exportXml", translate("menu.file.export.xml"), "exportxml32", this::exportXmlActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/file/export/exportAll", translate("menu.file.export.all"), "export16", this::exportAllActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+E"), false); - addMenuItem("/file/export/exportSelected", translate("menu.file.export.selection"), "exportsel16", this::exportSelectedActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - finishMenu("/file/export"); - - addMenuItem("/file/import", translate("menu.import"), null, null, 0, null, false, null, false); - addMenuItem("/file/import/importXml", translate("menu.file.import.xml"), "importxml32", this::importXmlActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("/file/import/importText", translate("menu.file.import.text"), "importtext32", this::importTextActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/file/import/importScript", translate("menu.file.import.script"), "importscript32", this::importScriptActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/file/import/importSymbolClass", translate("menu.file.import.symbolClass"), "importsymbolclass32", this::importSymbolClassActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - finishMenu("/file/import"); - - addMenuItem("/file/start", translate("menu.file.start"), null, null, 0, null, false, null, false); - addMenuItem("/file/start/run", translate("menu.file.start.run"), "play32", this::runActionPerformed, PRIORITY_TOP, null, true, new HotKey("F6"), false); - addMenuItem("/file/start/debug", translate("menu.file.start.debug"), "debug32", this::debugActionPerformed, PRIORITY_TOP, null, true, new HotKey("CTRL+F5"), false); - addMenuItem("/file/start/stop", translate("menu.file.start.stop"), "stop32", this::stopActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("/file/start/debugpcode", translate("menu.file.start.debugpcode"), "debug32", this::debugPCodeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - finishMenu("/file/start"); - - addMenuItem("/file/view", translate("menu.view"), null, null, 0, null, false, null, false); - addToggleMenuItem("/file/view/viewResources", translate("menu.file.view.resources"), "view", "viewresources16", this::viewResourcesActionPerformed, PRIORITY_MEDIUM, null); - addToggleMenuItem("/file/view/viewHex", translate("menu.file.view.hex"), "view", "viewhex16", this::viewHexActionPerformed, PRIORITY_MEDIUM, null); - finishMenu("/file/view"); - - addSeparator("/file"); - addMenuItem("/file/close", translate("menu.file.close"), "close32", this::closeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/file/closeAll", translate("menu.file.closeAll"), "closeall32", this::closeAllActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+X"), false); - - if (!supportsAppMenu()) { - addSeparator("/file"); - addMenuItem("/file/exit", translate("menu.file.exit"), "exit32", this::exitActionPerformed, PRIORITY_TOP, null, true, null, false); - } - - finishMenu("/file"); - - if (Configuration.dumpView.get()) { - setGroupSelection("view", "/file/view/viewHex"); - } else { - setGroupSelection("view", "/file/view/viewResources"); - } - - /* - menu.file.start = Start - menu.file.start.run = Run - menu.file.start.stop = Stop - menu.file.start.debug = Debug - menu.debugging = Debugging - menu.debugging.debug = Debug - menu.debugging.debug.stop = Stop - menu.debugging.debug.pause = Pause - menu.debugging.debug.stepOver = Step over - menu.debugging.debug.stepInto = Step into - menu.debugging.debug.stepOut = Step out - menu.debugging.debug.continue = Continue - menu.debugging.debug.stack = Stack... - menu.debugging.debug.watch = New watch... - */ - addMenuItem("/debugging", translate("menu.debugging"), null, null, 0, null, false, null, true); - addMenuItem("/debugging/debug", translate("menu.debugging.debug"), null, null, 0, null, false, null, false); - addMenuItem("/debugging/debug/stop", translate("menu.file.start.stop"), "stop32", this::stopActionPerformed, PRIORITY_TOP, null, true, null, false); - //addMenuItem("/debugging/debug/pause", translate("menu.debugging.debug.pause"), "pause32", this::pauseActionPerformed, PRIORITY_TOP, null, true,false); - addMenuItem("/debugging/debug/continue", translate("menu.debugging.debug.continue"), "continue32", this::continueActionPerformed, PRIORITY_TOP, null, true, new HotKey("F5"), false); - addMenuItem("/debugging/debug/stepOver", translate("menu.debugging.debug.stepOver"), "stepover32", this::stepOverActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("F8"), false); - addMenuItem("/debugging/debug/stepInto", translate("menu.debugging.debug.stepInto"), "stepinto32", this::stepIntoActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("F7"), false); - addMenuItem("/debugging/debug/stepOut", translate("menu.debugging.debug.stepOut"), "stepout32", this::stepOutActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+F7"), false); - //addMenuItem("/debugging/debug/stack", translate("menu.debugging.debug.stack"), "stack32", this::stackActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - //addMenuItem("/debugging/debug/watch", translate("menu.debugging.debug.watch"), "watch32", this::watchActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - finishMenu("/debugging/debug"); - finishMenu("/debugging"); - - addMenuItem("/tools", translate("menu.tools"), null, null, 0, null, false, null, false); - addMenuItem("/tools/search", translate("menu.tools.search"), "search16", this::searchActionPerformed, PRIORITY_TOP, null, true, null, false); - - addMenuItem("/tools/replace", translate("menu.tools.replace"), "replace32", this::replaceActionPerformed, PRIORITY_TOP, null, true, null, false); - addToggleMenuItem("/tools/timeline", translate("menu.tools.timeline"), null, "timeline32", this::timelineActionPerformed, PRIORITY_TOP, null); - - addMenuItem("/tools/showProxy", translate("menu.tools.proxy"), "proxy16", this::showProxyActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - if (Platform.isWindows()) { - addMenuItem("/tools/searchMemory", translate("menu.tools.searchMemory"), "loadmemory16", this::searchMemoryActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - } - - //addMenuItem("/tools/searchCache", translate("menu.tools.searchCache"), "loadcache16", this::searchCacheActionPerformed, PRIORITY_MEDIUM, null, true, null); - addMenuItem("/tools/deobfuscation", translate("menu.tools.deobfuscation"), "deobfuscate16", null, 0, null, false, null, false); - addMenuItem("/tools/deobfuscation/renameOneIdentifier", translate("menu.tools.deobfuscation.globalrename"), "rename16", this::renameOneIdentifier, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/tools/deobfuscation/renameInvalidIdentifiers", translate("menu.tools.deobfuscation.renameinvalid"), "renameall16", this::renameInvalidIdentifiers, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/tools/deobfuscation/deobfuscation", translate("menu.tools.deobfuscation.pcode"), "deobfuscate32", this::deobfuscationActionPerformed, PRIORITY_TOP, null, true, null, false); - finishMenu("/tools/deobfuscation"); - - /*addMenuItem("/tools/debugger", translate("menu.debugger"), null, null, 0, null, false, null,false); - addToggleMenuItem("/tools/debugger/debuggerSwitch", translate("menu.debugger.switch"), null, "debugger32", this::debuggerSwitchActionPerformed, PRIORITY_TOP, null,false); - addMenuItem("/tools/debugger/debuggerReplaceTrace", translate("menu.debugger.replacetrace"), "debuggerreplace16", this::debuggerReplaceTraceCallsActionPerformed, PRIORITY_MEDIUM, null, true, null,false); - //addMenuItem("/tools/debugger/debuggerInjectLoader", "Inject Loader", "debuggerreplace16", this::debuggerInjectLoader, PRIORITY_MEDIUM, null, true,false); - addMenuItem("/tools/debugger/debuggerShowLog", translate("menu.debugger.showlog"), "debuggerlog16", this::debuggerShowLogActionPerformed, PRIORITY_MEDIUM, null, true, null,false); - finishMenu("/tools/debugger");*/ - addMenuItem("/tools/gotoDocumentClass", translate("menu.tools.gotoDocumentClass"), "gotomainclass32", this::gotoDucumentClassActionPerformed, PRIORITY_TOP, null, true, null, false); - finishMenu("/tools"); - - //Settings - addMenuItem("/settings", translate("menu.settings"), null, null, 0, null, false, null, false); - - addToggleMenuItem("/settings/autoDeobfuscation", translate("menu.settings.autodeobfuscation"), null, null, this::autoDeobfuscationActionPerformed, 0, null); - addToggleMenuItem("/settings/simplifyExpressions", translate("menu.settings.simplifyExpressions"), null, null, this::simplifyExpressionsActionPerformed, 0, null); - addToggleMenuItem("/settings/internalViewer", translate("menu.settings.internalflashviewer"), null, null, this::internalViewerSwitchActionPerformed, 0, null); - addToggleMenuItem("/settings/parallelSpeedUp", translate("menu.settings.parallelspeedup"), null, null, this::parallelSpeedUpActionPerformed, 0, null); - addToggleMenuItem("/settings/disableDecompilation", translate("menu.settings.disabledecompilation"), null, null, this::disableDecompilationActionPerformed, 0, null); - //addToggleMenuItem("/settings/cacheOnDisk", translate("menu.settings.cacheOnDisk"), null, null, this::cacheOnDiskActionPerformed, 0, null); - addToggleMenuItem("/settings/gotoMainClassOnStartup", translate("menu.settings.gotoMainClassOnStartup"), null, null, this::gotoDucumentClassOnStartupActionPerformed, 0, null); - addToggleMenuItem("/settings/autoRenameIdentifiers", translate("menu.settings.autoRenameIdentifiers"), null, null, this::autoRenameIdentifiersActionPerformed, 0, null); - addToggleMenuItem("/settings/autoOpenLoadedSWFs", translate("menu.settings.autoOpenLoadedSWFs"), null, null, this::autoOpenLoadedSWFsActionPerformed, 0, null); - if (Platform.isWindows()) { - addToggleMenuItem("/settings/associate", translate("menu.settings.addtocontextmenu"), null, null, this::associateActionPerformed, 0, null); - } - - addMenuItem("/settings/language", translate("menu.language"), null, null, 0, null, false, null, false); - addMenuItem("/settings/language/setLanguage", translate("menu.settings.language"), "setlanguage32", this::setLanguageActionPerformed, PRIORITY_TOP, null, true, null, false); - finishMenu("/settings/language"); - - /*addMenuItem("/settings/deobfuscation", translate("menu.deobfuscation"), null, null, 0, null, false,false); - addToggleMenuItem("/settings/deobfuscation/old", translate("menu.file.deobfuscation.old"), "deobfuscation", "deobfuscateold16", (ActionEvent e) -> { - deobfuscationMode(e, 0); - }, 0); - addToggleMenuItem("/settings/deobfuscation/new", translate("menu.file.deobfuscation.new"), "deobfuscation", "deobfuscatenew16", (ActionEvent e) -> { - deobfuscationMode(e, 1); - }, 0); - - finishMenu("/settings/deobfuscation");*/ - addMenuItem("/settings/advancedSettings", translate("menu.advancedsettings.advancedsettings"), null, null, 0, null, false, null, false); - addMenuItem("/settings/advancedSettings/advancedSettings", translate("menu.advancedsettings.advancedsettings"), "settings32", this::advancedSettingsActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("/settings/advancedSettings/clearRecentFiles", translate("menu.tools.otherTools.clearRecentFiles"), "clearrecent16", this::clearRecentFilesActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - finishMenu("/settings/advancedSettings"); - - finishMenu("/settings"); - - setMenuChecked("/settings/autoDeobfuscation", Configuration.autoDeobfuscate.get()); - Configuration.autoDeobfuscate.addListener(configListenerAutoDeobfuscate = (Boolean newValue) -> { - setMenuChecked("/settings/autoDeobfuscation", newValue); - }); - - setMenuChecked("/settings/simplifyExpressions", Configuration.simplifyExpressions.get()); - Configuration.simplifyExpressions.addListener(configListenerSimplifyExpressions = (Boolean newValue) -> { - setMenuChecked("/settings/simplifyExpressions", newValue); - }); - - setMenuChecked("/settings/internalViewer", Configuration.internalFlashViewer.get() || externalFlashPlayerUnavailable); - Configuration.internalFlashViewer.addListener(configListenerInternalFlashViewer = (Boolean newValue) -> { - setMenuChecked("/settings/internalViewer", newValue || externalFlashPlayerUnavailable); - }); - - setMenuChecked("/settings/parallelSpeedUp", Configuration.parallelSpeedUp.get()); - Configuration.parallelSpeedUp.addListener(configListenerParallelSpeedUp = (Boolean newValue) -> { - setMenuChecked("/settings/parallelSpeedUp", newValue); - }); - - setMenuChecked("/settings/disableDecompilation", !Configuration.decompile.get()); - Configuration.decompile.addListener(configListenerDecompile = (Boolean newValue) -> { - setMenuChecked("/settings/disableDecompilation", !newValue); - }); - - /*setMenuChecked("/settings/cacheOnDisk", Configuration.cacheOnDisk.get()); - Configuration.cacheOnDisk.addListener(configListenerCacheOnDisk = (Boolean newValue) -> { - setMenuChecked("/settings/cacheOnDisk", newValue); - });*/ - setMenuChecked("/settings/gotoMainClassOnStartup", Configuration.gotoMainClassOnStartup.get()); - Configuration.gotoMainClassOnStartup.addListener(configListenerGotoMainClassOnStartup = (Boolean newValue) -> { - setMenuChecked("/settings/gotoMainClassOnStartup", newValue); - }); - - setMenuChecked("/settings/autoRenameIdentifiers", Configuration.autoRenameIdentifiers.get()); - Configuration.autoRenameIdentifiers.addListener(configListenerAutoRenameIdentifiers = (Boolean newValue) -> { - setMenuChecked("/settings/autoRenameIdentifiers", newValue); - }); - - setMenuChecked("/settings/autoOpenLoadedSWFs", Configuration.autoOpenLoadedSWFs.get()); - Configuration.autoOpenLoadedSWFs.addListener(configListenerAutoOpenLoadedSWFs = (Boolean newValue) -> { - setMenuChecked("/settings/autoOpenLoadedSWFs", newValue); - }); - - if (externalFlashPlayerUnavailable) { - setMenuEnabled("/settings/internalViewer", false); - setMenuEnabled("/settings/autoOpenLoadedSWFs", false); - } - - /*int deobfuscationMode = Configuration.deobfuscationMode.get(); - switch (deobfuscationMode) { - case 0: - setGroupSelection("deobfuscation", "/settings/deobfuscation/old"); - break; - case 1: - setGroupSelection("deobfuscation", "/settings/deobfuscation/new"); - break; - }*/ - if (Platform.isWindows()) { - setMenuChecked("/settings/associate", ContextMenuTools.isAddedToContextMenu()); - } - - //Help - addMenuItem("/help", translate("menu.help"), null, null, 0, null, false, null, false); - addMenuItem("/help/helpUs", translate("menu.help.helpus"), "donate32", this::helpUsActionPerformed, PRIORITY_TOP, null, true, null, false); - addMenuItem("/help/homePage", translate("menu.help.homepage"), "homepage16", this::homePageActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addSeparator("/help"); - addMenuItem("/help/checkUpdates", translate("menu.help.checkupdates"), "update16", this::checkUpdatesActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/help/about", translate("menu.help.about"), "about32", this::aboutActionPerformed, PRIORITY_TOP, null, true, null, false); - finishMenu("/help"); - - if (Configuration._showDebugMenu.get() || Configuration._debugMode.get()) { - - addMenuItem("/debug", "# FFDec Debug #", null, null, 0, null, false, null, false); - addMenuItem("/debug/removeNonScripts", "Remove non scripts", "continue16", e -> removeNonScripts(), PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/debug/removeExceptSelected", "Remove except selected", "continue16", e -> removeExceptSelected(), PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/debug/refreshDecompiled", "Refresh decompiled script", "continue16", e -> refreshDecompiled(), PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/debug/checkResources", "Check resources", "continue16", e -> checkResources(), PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/debug/callGc", "Call System.gc()", "continue16", e -> System.gc(), PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/debug/emptyCache", "Empty cache", "continue16", e -> { - SWF nswf = mainFrame.getPanel().getCurrentSwf(); - if (nswf != null) { - nswf.clearAllCache(); - } - }, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/debug/memoryInformation", "Memory information", "continue16", e -> { - String architecture = System.getProperty("sun.arch.data.model"); - Runtime runtime = Runtime.getRuntime(); - String info = "Architecture: " + architecture + Helper.newLine - + "Jre 64bit: " + Helper.is64BitJre() + Helper.newLine - + "Os 64bit: " + Helper.is64BitOs() + Helper.newLine - + "Max: " + (runtime.maxMemory() / 1024 / 1024) + "MB" + Helper.newLine - + "Used: " + (runtime.totalMemory() / 1024 / 1024) + "MB" + Helper.newLine - + "Free: " + (runtime.freeMemory() / 1024 / 1024) + "MB"; - View.showMessageDialog(null, info); - SWF nswf = mainFrame.getPanel().getCurrentSwf(); - if (nswf != null) { - nswf.clearAllCache(); - } - }, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/debug/fixAs3Code", "Fix AS3 code", "continue16", e -> { - SWF nswf = mainFrame.getPanel().getCurrentSwf(); - if (nswf != null) { - nswf.fixAS3Code(); - } - }, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/debug/openTestSwfs", "Open test SWFs", "continue16", e -> { - String path; - - SWFSourceInfo[] sourceInfos = new SWFSourceInfo[2]; - String mainPath = Main.class.getProtectionDomain().getCodeSource().getLocation().getPath(); - path = mainPath + "\\..\\..\\libsrc\\ffdec_lib\\testdata\\as2\\as2.swf"; - sourceInfos[0] = new SWFSourceInfo(null, path, null); - path = mainPath + "\\..\\..\\libsrc\\ffdec_lib\\testdata\\as3\\as3.swf"; - sourceInfos[1] = new SWFSourceInfo(null, path, null); - Main.openFile(sourceInfos); - }, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/debug/createNewSwf", "Create new SWF", "continue16", e -> { - SWF swf = new SWF(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - swf.saveTo(baos); - } catch (IOException ex) { - Logger.getLogger(MainFrameMenu.class.getName()).log(Level.SEVERE, null, ex); - } - - Main.openFile(new SWFSourceInfo(new ByteArrayInputStream(baos.toByteArray()), "New SWF", "New SWF")); - }, PRIORITY_MEDIUM, null, true, null, false); - finishMenu("/debug"); - } - - finishMenu(""); - } - - private void viewResourcesActionPerformed(ActionEvent evt) { - Configuration.dumpView.set(false); - mainFrame.getPanel().showView(MainPanel.VIEW_RESOURCES); - setGroupSelection("view", "/file/view/viewResources"); - setMenuChecked("/tools/timeline", false); - } - - private void viewHexActionPerformed(ActionEvent evt) { - Configuration.dumpView.set(true); - MainPanel mainPanel = mainFrame.getPanel(); - if (mainPanel.isModified()) { - View.showMessageDialog(null, translate("message.warning.hexViewNotUpToDate"), translate("message.warning"), JOptionPane.WARNING_MESSAGE, Configuration.warningHexViewNotUpToDate); - } - - mainPanel.showView(MainPanel.VIEW_DUMP); - setGroupSelection("view", "/file/view/viewHex"); - setMenuChecked("/tools/timeline", false); - } - - private void debuggerSwitchActionPerformed(ActionEvent evt) { - boolean debuggerOn = isMenuChecked("/tools/debugger/debuggerSwitch"); - if (!debuggerOn || View.showConfirmDialog((Component) mainFrame, translate("message.debugger"), translate("dialog.message.title"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE, Configuration.displayDebuggerInfo, JOptionPane.OK_OPTION) == JOptionPane.OK_OPTION) { - switchDebugger(); - mainFrame.getPanel().refreshDecompiled(); - } else { - setMenuChecked("/tools/debugger/debuggerSwitch", false); - } - setMenuEnabled("/tools/debugger/debuggerReplaceTrace", isMenuChecked("/tools/debugger/debuggerSwitch")); - //setMenuEnabled("/tools/debugger/debuggerInjectLoader", isMenuChecked("/tools/debugger/debuggerSwitch")); - } - - private void timelineActionPerformed(ActionEvent evt) { - if (isMenuChecked("/tools/timeline")) { - if (!mainFrame.getPanel().showView(MainPanel.VIEW_TIMELINE)) { - setMenuChecked("/tools/timeline", false); - } else { - setGroupSelection("view", null); - } - } else if (Configuration.dumpView.get()) { - setGroupSelection("view", "/file/view/viewHex"); - mainFrame.getPanel().showView(MainPanel.VIEW_DUMP); - } else { - setGroupSelection("view", "/file/view/viewResources"); - mainFrame.getPanel().showView(MainPanel.VIEW_RESOURCES); - } - } - - protected void loadRecent(ActionEvent evt) { - List recentFiles = Configuration.getRecentFiles(); - clearMenu("/file/" + (supportsMenuAction() ? "open" : "recent")); - clearMenu("_/open"); - - for (int i = recentFiles.size() - 1; i >= 0; i--) { - final String f = recentFiles.get(i); - ActionListener a = (ActionEvent e) -> { - if (Main.openFile(f, null) == OpenFileResult.NOT_FOUND) { - if (View.showConfirmDialog(null, translate("message.confirm.recentFileNotFound"), translate("message.confirm"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_NO_OPTION) { - Configuration.removeRecentFile(f); - } - } - }; - addMenuItem("/file/" + (supportsMenuAction() ? "open" : "recent") + "/" + i, f, null, a, 0, null, true, null, false); - addMenuItem("_/open/" + i, f, null, a, 0, null, true, null, false); - } - - finishMenu("/file/" + (supportsMenuAction() ? "open" : "recent")); - finishMenu("_/open"); - } - - public void dispose() { - KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - manager.removeKeyEventDispatcher(keyEventDispatcher); - - Configuration.autoDeobfuscate.removeListener(configListenerAutoDeobfuscate); - Configuration.simplifyExpressions.removeListener(configListenerSimplifyExpressions); - Configuration.internalFlashViewer.removeListener(configListenerInternalFlashViewer); - Configuration.parallelSpeedUp.removeListener(configListenerParallelSpeedUp); - Configuration.decompile.removeListener(configListenerDecompile); - //Configuration.cacheOnDisk.removeListener(configListenerCacheOnDisk); - Configuration.gotoMainClassOnStartup.removeListener(configListenerGotoMainClassOnStartup); - Configuration.autoRenameIdentifiers.removeListener(configListenerAutoRenameIdentifiers); - Configuration.autoOpenLoadedSWFs.removeListener(configListenerAutoOpenLoadedSWFs); - - Main.stopRun(); - } - - public boolean runActionPerformed(ActionEvent evt) { - Main.run(swf); - return true; - } - - public boolean debugActionPerformed(ActionEvent evt) { - Main.runDebug(swf, false); - return true; - } - - public boolean debugPCodeActionPerformed(ActionEvent evt) { - Main.runDebug(swf, true); - return true; - } - - public boolean stopActionPerformed(ActionEvent evt) { - Main.stopRun(); - return true; - } - - public boolean pauseActionPerformed(ActionEvent evt) { - try { - DebuggerCommands cmd = Main.getDebugHandler().getCommands(); - //TODO - - } catch (IOException ex) { - Main.getDebugHandler().disconnect(); - //ignore - } - return true; - } - - public boolean stepOverActionPerformed(ActionEvent evt) { - - try { - - DebuggerCommands cmd = Main.getDebugHandler().getCommands(); - mainFrame.getPanel().clearDebuggerColors(); - Main.startWork(AppStrings.translate("work.debugging") + "...", null); - - cmd.stepOver(); - } catch (IOException ex) { - Main.getDebugHandler().disconnect(); - //ignore - } - return true; - } - - public boolean stepIntoActionPerformed(ActionEvent evt) { - try { - DebuggerCommands cmd = Main.getDebugHandler().getCommands(); - mainFrame.getPanel().clearDebuggerColors(); - Main.startWork(AppStrings.translate("work.debugging") + "...", null); - - cmd.stepInto(); - } catch (IOException ex) { - Main.getDebugHandler().disconnect(); - //ignore - } - - return true; - } - - public boolean stepOutActionPerformed(ActionEvent evt) { - try { - DebuggerCommands cmd = Main.getDebugHandler().getCommands(); - mainFrame.getPanel().clearDebuggerColors(); - Main.startWork(AppStrings.translate("work.debugging") + "...", null); - cmd.stepOut(); - } catch (IOException ex) { - Main.getDebugHandler().disconnect(); - //ignore - } - - return true; - } - - public boolean continueActionPerformed(ActionEvent evt) { - try { - DebuggerCommands cmd = Main.getDebugHandler().getCommands(); - mainFrame.getPanel().clearDebuggerColors(); - Main.startWork(AppStrings.translate("work.debugging") + "...", null); - cmd.sendContinue(); - } catch (IOException ex) { - Main.getDebugHandler().disconnect(); - //ignore - } - - return true; - } - - public boolean stackActionPerformed(ActionEvent evt) { - //TODO - return true; - } - - public boolean watchActionPerformed(ActionEvent evt) { - //TODO - return true; - } - - public boolean dispatchKeyEvent(KeyEvent e) { - if (((JFrame) mainFrame).isActive() && e.getID() == KeyEvent.KEY_PRESSED) { - - HotKey ek = new HotKey(e); - for (String path : menuHotkeys.keySet()) { - HotKey mk = menuHotkeys.get(path); - if (ek.equals(mk)) { - if (menuActions.containsKey(path)) { - menuActions.get(path).actionPerformed(null); - return true; - } - } - } - - //other nonmenu actions - int code = e.getKeyCode(); - if (e.isControlDown() && e.isShiftDown()) { //CTRL+SHIFT - switch (code) { - case KeyEvent.VK_F: - return searchInActionPerformed(null); - case KeyEvent.VK_T: - return searchInTextPerformed(null); - case KeyEvent.VK_D: - return clearLog(null); - } - } else if (e.isControlDown() && !e.isShiftDown()) { //CTRL - switch (code) { - case KeyEvent.VK_UP: - return previousTag(null); - case KeyEvent.VK_DOWN: - return nextTag(null); - } - } - } - - return false; - } - - public abstract void hilightPath(String path); - - public abstract void setPathVisible(String path, boolean val); -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.debugger.flash.DebuggerCommands; +import com.jpexs.decompiler.flash.ApplicationInfo; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFBundle; +import com.jpexs.decompiler.flash.SWFSourceInfo; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.configuration.ConfigurationItemChangeListener; +import com.jpexs.decompiler.flash.console.ContextMenuTools; +import com.jpexs.decompiler.flash.gui.debugger.DebuggerTools; +import com.jpexs.decompiler.flash.gui.helpers.CheckResources; +import com.jpexs.decompiler.flash.tags.ABCContainerTag; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.utf8.Utf8Helper; +import com.sun.jna.Platform; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.KeyEventDispatcher; +import java.awt.KeyboardFocusManager; +import java.awt.ScrollPane; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.WindowEvent; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.AbstractButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JOptionPane; + +/** + * + * @author JPEXS + */ +public abstract class MainFrameMenu implements MenuBuilder { + + private final MainFrame mainFrame; + + private KeyEventDispatcher keyEventDispatcher; + + private SWF swf; + + private ConfigurationItemChangeListener configListenerAutoDeobfuscate; + + private ConfigurationItemChangeListener configListenerSimplifyExpressions; + + private ConfigurationItemChangeListener configListenerInternalFlashViewer; + + private ConfigurationItemChangeListener configListenerParallelSpeedUp; + + private ConfigurationItemChangeListener configListenerDecompile; + + //private ConfigurationItemChangeListener configListenerCacheOnDisk; + private ConfigurationItemChangeListener configListenerGotoMainClassOnStartup; + + private ConfigurationItemChangeListener configListenerAutoRenameIdentifiers; + + private ConfigurationItemChangeListener configListenerAutoOpenLoadedSWFs; + + protected final Map menuHotkeys = new HashMap<>(); + + @Override + public HotKey getMenuHotkey(String path) { + return menuHotkeys.get(path); + } + + protected final Map menuActions = new HashMap<>(); + + public boolean isInternalFlashViewerSelected() { + return isMenuChecked("/settings/internalViewer"); //miInternalViewer.isSelected(); + } + + private final boolean externalFlashPlayerUnavailable; + + public MainFrameMenu(MainFrame mainFrame, boolean externalFlashPlayerUnavailable) { + registerHotKeys(); + this.mainFrame = mainFrame; + this.externalFlashPlayerUnavailable = externalFlashPlayerUnavailable; + } + + protected String translate(String key) { + return mainFrame.translate(key); + } + + protected boolean openActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return false; + } + + Main.openFileDialog(); + return true; + } + + protected boolean saveActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return false; + } + + if (swf != null) { + boolean saved = false; + if (swf.swfList != null && swf.swfList.isBundle()) { + SWFBundle bundle = swf.swfList.bundle; + if (!bundle.isReadOnly()) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + swf.saveTo(baos); + saved = bundle.putSWF(swf.getFileTitle(), new ByteArrayInputStream(baos.toByteArray())); + } catch (IOException ex) { + Logger.getLogger(MainFrameMenu.class.getName()).log(Level.SEVERE, "Cannot save SWF", ex); + } + } + } else if (swf.binaryData != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + swf.saveTo(baos); + swf.binaryData.binaryData = new ByteArrayRange(baos.toByteArray()); + swf.binaryData.setModified(true); + saved = true; + } catch (IOException ex) { + Logger.getLogger(MainFrameMenu.class.getName()).log(Level.SEVERE, "Cannot save SWF", ex); + } + } else if (swf.getFile() == null) { + saved = saveAs(swf, SaveFileMode.SAVEAS); + } else { + try { + Main.saveFile(swf, swf.getFile()); + saved = true; + } catch (IOException ex) { + Logger.getLogger(MainFrameMenu.class.getName()).log(Level.SEVERE, null, ex); + View.showMessageDialog(null, translate("error.file.save"), translate("error"), JOptionPane.ERROR_MESSAGE); + } + } + if (saved) { + swf.clearModified(); + mainFrame.getPanel().refreshTree(swf); + } + + return true; + } + + return false; + } + + protected boolean saveAsActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return false; + } + + if (swf != null) { + if (saveAs(swf, SaveFileMode.SAVEAS)) { + swf.clearModified(); + } + + return true; + } + + return false; + } + + private boolean saveAs(SWF swf, SaveFileMode mode) { + if (Main.saveFileDialog(swf, mode)) { + mainFrame.setTitle(ApplicationInfo.applicationVerName + (Configuration.displayFileName.get() ? " - " + swf.getFileTitle() : "")); + updateComponents(swf); + return true; + } + return false; + } + + protected void saveAsExeActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + if (swf != null) { + saveAs(swf, SaveFileMode.EXE); + } + } + + protected void closeActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + if (swf == null) { + return; + } + + Main.closeFile(swf.swfList); + } + + protected boolean closeAllActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return false; + } + + if (swf != null) { + return Main.closeAll(); + } + + return false; + } + + protected void importTextActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + mainFrame.getPanel().importText(swf); + } + + protected void importScriptActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + mainFrame.getPanel().importScript(swf); + } + + protected void importSymbolClassActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + mainFrame.getPanel().importSymbolClass(swf); + } + + protected boolean exportAllActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return false; + } + + return export(false); + } + + protected boolean exportSelectedActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return false; + } + + return export(true); + } + + protected boolean export(boolean onlySelected) { + if (swf != null) { + mainFrame.getPanel().export(onlySelected); + return true; + } + + return false; + } + + protected void exportFlaActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + mainFrame.getPanel().exportFla(swf); + } + + protected void importXmlActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + mainFrame.getPanel().importSwfXml(); + } + + protected void exportXmlActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + mainFrame.getPanel().exportSwfXml(); + } + + protected boolean searchActionPerformed(ActionEvent evt) { + return search(evt, null); + } + + protected boolean searchInTextPerformed(ActionEvent evt) { + return search(evt, true); + } + + protected boolean searchInActionPerformed(ActionEvent evt) { + return search(evt, false); + } + + protected boolean search(ActionEvent evt, Boolean searchInText) { + if (swf != null) { + mainFrame.getPanel().searchInActionScriptOrText(searchInText, swf); + return true; + } + + return false; + } + + protected boolean replaceActionPerformed(ActionEvent evt) { + if (swf != null) { + mainFrame.getPanel().replaceText(); + return true; + } + + return false; + } + + protected void showProxyActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + Main.showProxy(); + } + + protected boolean clearLog(ActionEvent evt) { + ErrorLogFrame.getInstance().clearLog(); + return true; + } + + protected void renameOneIdentifier(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + mainFrame.getPanel().renameOneIdentifier(swf); + } + + protected void renameInvalidIdentifiers(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + mainFrame.getPanel().renameIdentifiers(swf); + } + + protected void deobfuscationActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + mainFrame.getPanel().deobfuscate(); + } + + protected void setSubLimiter(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + AbstractButton button = (AbstractButton) evt.getSource(); + boolean selected = button.isSelected(); + Main.setSubLimiter(selected); + } + + protected void switchDebugger() { + DebuggerTools.switchDebugger(swf); + } + + protected void debuggerShowLogActionPerformed(ActionEvent evt) { + DebuggerTools.debuggerShowLog(); + } + + protected void debuggerInjectLoader(ActionEvent evt) { + DebuggerTools.injectDebugLoader(swf); + refreshDecompiled(); + } + + protected void debuggerReplaceTraceCallsActionPerformed(ActionEvent evt) { + ReplaceTraceDialog rtd = new ReplaceTraceDialog(Configuration.lastDebuggerReplaceFunction.get()); + rtd.setVisible(true); + if (rtd.getValue() != null) { + String fname = rtd.getValue(); + DebuggerTools.replaceTraceCalls(swf, fname); + mainFrame.getPanel().refreshDecompiled(); + Configuration.lastDebuggerReplaceFunction.set(rtd.getValue()); + } + } + + protected void clearRecentFilesActionPerformed(ActionEvent evt) { + Configuration.recentFiles.set(null); + } + + protected void removeNonScripts() { + mainFrame.getPanel().removeNonScripts(swf); + } + + protected void removeExceptSelected() { + mainFrame.getPanel().removeExceptSelected(swf); + } + + protected void refreshDecompiled() { + mainFrame.getPanel().refreshDecompiled(); + } + + protected boolean previousTag(ActionEvent evt) { + return mainFrame.getPanel().previousTag(); + } + + protected boolean nextTag(ActionEvent evt) { + return mainFrame.getPanel().nextTag(); + } + + protected void checkResources() { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(os); + 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)); + Container cnt = getContentPane(); + cnt.setLayout(new BorderLayout()); + String[] languages = SelectLanguageDialog.getAvailableLanguages().clone(); + languages[0] = "all"; + JComboBox languagesComboBox = new JComboBox<>(languages); + this.add(languagesComboBox, BorderLayout.NORTH); + ScrollPane scrollPane = new ScrollPane(); + JEditorPane editor = new JEditorPane(); + editor.setEditable(false); + editor.setText(str); + scrollPane.add(editor); + this.add(scrollPane, BorderLayout.CENTER); + this.setModal(true); + View.centerScreen(this); + languagesComboBox.addActionListener((ActionEvent e) -> { + String lang = (String) languagesComboBox.getSelectedItem(); + if (lang.equals("all")) { + lang = null; + } + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try (PrintStream stream = new PrintStream(os, false, "UTF-8")) { + CheckResources.checkResources(stream, lang); + String str = new String(os.toByteArray(), Utf8Helper.charset); + editor.setText(str); + } catch (UnsupportedEncodingException ex) { + // ignore + } + }); + super.setVisible(bln); + } + }; + dialog.setVisible(true); + } + + protected void checkUpdatesActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + if (!Main.checkForUpdates()) { + View.showMessageDialog(null, translate("update.check.nonewversion"), translate("update.check.title"), JOptionPane.INFORMATION_MESSAGE); + } + } + + protected void helpUsActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + String helpUsURL = ApplicationInfo.PROJECT_PAGE + "/help_us.html?utm_source=app&utm_medium=menu&utm_campaign=app"; + if (!View.navigateUrl(helpUsURL)) { + View.showMessageDialog(null, translate("message.helpus").replace("%url%", helpUsURL)); + } + } + + protected void homePageActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + String homePageURL = ApplicationInfo.PROJECT_PAGE + "?utm_source=app&utm_medium=menu&utm_campaign=app"; + if (!View.navigateUrl(homePageURL)) { + View.showMessageDialog(null, translate("message.homepage").replace("%url%", homePageURL)); + } + } + + protected void aboutActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + + Main.about(); + } + + protected boolean reloadActionPerformed(ActionEvent evt) { + if (swf != null) { + if (View.showConfirmDialog(null, translate("message.confirm.reload"), translate("message.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) { + Main.reloadFile(swf.swfList); + } + } + return true; + } + + protected boolean reloadAllActionPerformed(ActionEvent evt) { + if (swf != null) { + if (View.showConfirmDialog(null, translate("message.confirm.reloadAll"), translate("message.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) { + Main.reloadApp(); + } + + return true; + } + + Main.reloadApp(); + return true; + } + + protected void advancedSettingsActionPerformed(ActionEvent evt) { + Main.advancedSettings(); + } + + protected void searchMemoryActionPerformed(ActionEvent evt) { + Main.loadFromMemory(); + } + + protected void searchCacheActionPerformed(ActionEvent evt) { + Main.loadFromCache(); + } + + protected void gotoDucumentClassOnStartupActionPerformed(ActionEvent evt) { + AbstractButton button = (AbstractButton) evt.getSource(); + boolean selected = button.isSelected(); + + Configuration.gotoMainClassOnStartup.set(selected); + } + + protected void autoOpenLoadedSWFsActionPerformed(ActionEvent evt) { + AbstractButton button = (AbstractButton) evt.getSource(); + boolean selected = button.isSelected(); + + Configuration.autoOpenLoadedSWFs.set(selected); + } + + protected void autoRenameIdentifiersActionPerformed(ActionEvent evt) { + AbstractButton button = (AbstractButton) evt.getSource(); + boolean selected = button.isSelected(); + + Configuration.autoRenameIdentifiers.set(selected); + } + + /*protected void cacheOnDiskActionPerformed(ActionEvent evt) { + AbstractButton button = (AbstractButton) evt.getSource(); + boolean selected = button.isSelected(); + + Configuration.cacheOnDisk.set(selected); + if (selected) { + Cache.setStorageType(Cache.STORAGE_FILES); + } else { + Cache.setStorageType(Cache.STORAGE_MEMORY); + } + }*/ + protected void setLanguageActionPerformed(ActionEvent evt) { + new SelectLanguageDialog().display(); + } + + protected void disableDecompilationActionPerformed(ActionEvent evt) { + AbstractButton button = (AbstractButton) evt.getSource(); + boolean selected = button.isSelected(); + + Configuration.decompile.set(!selected); + mainFrame.getPanel().disableDecompilationChanged(); + } + + protected void associateActionPerformed(ActionEvent evt) { + AbstractButton button = (AbstractButton) evt.getSource(); + boolean selected = button.isSelected(); + + if (selected == ContextMenuTools.isAddedToContextMenu()) { + return; + } + ContextMenuTools.addToContextMenu(selected, false); + + // Update checkbox menuitem accordingly (User can cancel rights elevation) + new Timer().schedule(new TimerTask() { + @Override + public void run() { + button.setSelected(ContextMenuTools.isAddedToContextMenu()); + } + }, 1000); // It takes some time registry change to apply + } + + protected void gotoDucumentClassActionPerformed(ActionEvent evt) { + mainFrame.getPanel().gotoDocumentClass(mainFrame.getPanel().getCurrentSwf()); + } + + protected void parallelSpeedUpActionPerformed(ActionEvent evt) { + AbstractButton button = (AbstractButton) evt.getSource(); + boolean selected = button.isSelected(); + + String confStr = translate("message.confirm.parallel") + "\r\n"; + if (selected) { + confStr += " " + translate("message.confirm.on"); + } else { + confStr += " " + translate("message.confirm.off"); + } + if (View.showConfirmDialog(null, confStr, translate("message.parallel"), JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) { + Configuration.parallelSpeedUp.set(selected); + } else { + button.setSelected(Configuration.parallelSpeedUp.get()); + } + } + + protected void internalViewerSwitchActionPerformed(ActionEvent evt) { + AbstractButton button = (AbstractButton) evt.getSource(); + boolean selected = button.isSelected(); + + Configuration.internalFlashViewer.set(selected); + mainFrame.getPanel().reload(true); + } + + protected void simplifyExpressionsActionPerformed(ActionEvent evt) { + AbstractButton button = (AbstractButton) evt.getSource(); + boolean selected = button.isSelected(); + + Configuration.simplifyExpressions.set(selected); + mainFrame.getPanel().autoDeobfuscateChanged(); + } + + protected void autoDeobfuscationActionPerformed(ActionEvent evt) { + AbstractButton button = (AbstractButton) evt.getSource(); + boolean selected = button.isSelected(); + + if (View.showConfirmDialog(mainFrame.getPanel(), translate("message.confirm.autodeobfuscate") + "\r\n" + (selected ? translate("message.confirm.on") : translate("message.confirm.off")), translate("message.confirm"), JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) { + Configuration.autoDeobfuscate.set(selected); + mainFrame.getPanel().autoDeobfuscateChanged(); + } else { + button.setSelected(Configuration.autoDeobfuscate.get()); + } + } + + /*protected void deobfuscationMode(ActionEvent evt, int mode) { + Configuration.deobfuscationMode.set(mode); + mainFrame.getPanel().autoDeobfuscateChanged(); + }*/ + protected void exitActionPerformed(ActionEvent evt) { + JFrame frame = (JFrame) mainFrame; + frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING)); + } + + public void updateComponents() { + updateComponents(swf); + } + + public void updateComponents(SWF swf) { + this.swf = swf; + boolean isRunning = Main.isRunning(); + boolean isDebugRunning = Main.isDebugRunning(); + boolean isDebugPaused = Main.isDebugPaused(); + + boolean isRunningOrDebugging = isRunning || isDebugRunning; + + boolean swfSelected = swf != null; + boolean isWorking = Main.isWorking(); + List abcList = swf != null ? swf.getAbcList() : null; + boolean hasAbc = swfSelected && abcList != null && !abcList.isEmpty(); + boolean hasDebugger = hasAbc && DebuggerTools.hasDebugger(swf); + MainPanel mainPanel = mainFrame.getPanel(); + boolean swfLoaded = mainPanel != null ? !mainPanel.getSwfs().isEmpty() : false; + + setMenuEnabled("_/open", !isWorking); + setMenuEnabled("/file/open", !isWorking); + setMenuEnabled("_/save", swfSelected && !isWorking); + setMenuEnabled("/file/save", swfSelected && !isWorking); + setMenuEnabled("_/saveAs", swfSelected && !isWorking); + setMenuEnabled("/file/saveAs", swfSelected && !isWorking); + setMenuEnabled("/file/saveAsExe", swfSelected && !isWorking); + setMenuEnabled("_/close", swfSelected && !isWorking); + setMenuEnabled("/file/close", swfSelected && !isWorking); + setMenuEnabled("_/closeAll", swfLoaded && !isWorking); + setMenuEnabled("/file/closeAll", swfLoaded && !isWorking); + + setMenuEnabled("/file/export", swfSelected); + setMenuEnabled("_/exportAll", swfSelected && !isWorking); + setMenuEnabled("/file/export/exportAll", swfSelected && !isWorking); + setMenuEnabled("_/exportFla", swfSelected && !isWorking); + setMenuEnabled("/file/export/exportFla", swfSelected && !isWorking); + setMenuEnabled("_/exportSelected", swfSelected && !isWorking); + setMenuEnabled("/file/export/exportSelected", swfSelected && !isWorking); + setMenuEnabled("/file/export/exportXml", swfSelected && !isWorking); + + setMenuEnabled("/file/import", swfSelected); + setMenuEnabled("/file/import/importText", swfSelected && !isWorking); + setMenuEnabled("/file/import/importScript", swfSelected && !isWorking); + setMenuEnabled("/file/import/importSymbolClass", swfSelected && !isWorking); + setMenuEnabled("/file/import/importXml", swfSelected && !isWorking); + + setMenuEnabled("/tools/deobfuscation", swfSelected); + setMenuEnabled("/tools/deobfuscation/renameOneIdentifier", swfSelected && !isWorking); + setMenuEnabled("/tools/deobfuscation/renameInvalidIdentifiers", swfSelected && !isWorking); + setMenuEnabled("/tools/deobfuscation/deobfuscation", hasAbc); + + setMenuEnabled("/tools/search", swfSelected); + setMenuEnabled("/tools/replace", swfSelected); + setMenuEnabled("/tools/timeline", swfSelected); + setMenuEnabled("/tools/showProxy", !isWorking); + + setMenuEnabled("/tools/gotoDocumentClass", hasAbc); + /*setMenuEnabled("/tools/debugger/debuggerSwitch", hasAbc); + setMenuChecked("/tools/debugger/debuggerSwitch", hasDebugger); + setMenuEnabled("/tools/debugger/debuggerReplaceTrace", hasAbc && hasDebugger);*/ + //setMenuEnabled("/tools/debugger/debuggerInjectLoader", hasAbc && hasDebugger); + + setMenuEnabled("_/checkUpdates", !isWorking); + setMenuEnabled("/help/checkUpdates", !isWorking); + setMenuEnabled("/help/helpUs", !isWorking); + setMenuEnabled("/help/homePage", !isWorking); + setMenuEnabled("_/about", !isWorking); + setMenuEnabled("/help/about", !isWorking); + + setMenuEnabled("/file/start/run", swfSelected && !isRunningOrDebugging); + setMenuEnabled("/file/start/debug", swfSelected && !isRunningOrDebugging); + setMenuEnabled("/file/start/debugpcode", swfSelected && !isRunningOrDebugging); + + setMenuEnabled("/file/start/stop", isRunningOrDebugging); + setMenuEnabled("/debugging/debug/stop", isRunningOrDebugging); //same as previous + + setPathVisible("/debugging", isDebugRunning); + setMenuEnabled("/debugging/debug", isDebugRunning); + //setMenuEnabled("/debugging/debug/pause", isDebugRunning); + setMenuEnabled("/debugging/debug/stepOver", isDebugPaused); + setMenuEnabled("/debugging/debug/stepInto", isDebugPaused); + setMenuEnabled("/debugging/debug/stepOut", isDebugPaused); + setMenuEnabled("/debugging/debug/continue", isDebugPaused); + //setMenuEnabled("/debugging/debug/stack", isDebugPaused); + //setMenuEnabled("/debugging/debug/watch", isDebugPaused); + + } + + private void registerHotKeys() { + + KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + manager.addKeyEventDispatcher(keyEventDispatcher = this::dispatchKeyEvent); + } + + public void createMenuBar() { + initMenu(); + + if (supportsAppMenu()) { + addMenuItem("_", null, null, null, 0, null, false, null, false); + addMenuItem("_/open", translate("menu.file.open"), "open32", this::openActionPerformed, PRIORITY_TOP, this::loadRecent, false, null, false); + addMenuItem("_/save", translate("menu.file.save"), "save32", this::saveActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("_/saveAs", translate("menu.file.saveas"), "saveas32", this::saveAsActionPerformed, PRIORITY_TOP, null, true, null, false); + addSeparator("_"); + addMenuItem("_/exportFla", translate("menu.file.export.fla"), "exportfla32", this::exportFlaActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("_/exportAll", translate("menu.file.export.all"), "export32", this::exportAllActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("_/exportSelected", translate("menu.file.export.selection"), "exportsel32", this::exportSelectedActionPerformed, PRIORITY_TOP, null, true, null, false); + addSeparator("_"); + addMenuItem("_/checkUpdates", translate("menu.help.checkupdates"), "update32", this::checkUpdatesActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("_/about", translate("menu.help.about"), "about32", this::aboutActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("_/close", translate("menu.file.close"), "close32", this::closeActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("_/closeAll", translate("menu.file.closeAll"), "closeall32", this::closeAllActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("_/$exit", translate("menu.file.exit"), "exit32", this::exitActionPerformed, PRIORITY_TOP, null, true, null, false); + finishMenu("_"); + } + + addMenuItem("/file", translate("menu.file"), null, null, 0, null, false, null, false); + addMenuItem("/file/open", translate("menu.file.open"), "open32", this::openActionPerformed, PRIORITY_TOP, this::loadRecent, !supportsMenuAction(), new HotKey("CTRL+SHIFT+O"), false); + + if (!supportsMenuAction()) { + addMenuItem("/file/recent", translate("menu.recentFiles"), null, null, 0, this::loadRecent, false, null, false); + finishMenu("/file/recent"); + } else { + finishMenu("/file/open"); + } + + addMenuItem("/file/save", translate("menu.file.save"), "save32", this::saveActionPerformed, PRIORITY_TOP, null, true, new HotKey("CTRL+SHIFT+S"), false); + addMenuItem("/file/saveAs", translate("menu.file.saveas"), "saveas16", this::saveAsActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+A"), false); + addMenuItem("/file/saveAsExe", translate("menu.file.saveasexe"), "saveasexe16", this::saveAsExeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/reload", translate("menu.file.reload"), "reload16", this::reloadActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+R"), false); + addMenuItem("/file/reloadAll", translate("menu.file.reloadAll"), "reload16", this::reloadAllActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + + addSeparator("/file"); + + addMenuItem("/file/export", translate("menu.export"), null, null, 0, null, false, null, false); + addMenuItem("/file/export/exportFla", translate("menu.file.export.fla"), "exportfla32", this::exportFlaActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("/file/export/exportXml", translate("menu.file.export.xml"), "exportxml32", this::exportXmlActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/export/exportAll", translate("menu.file.export.all"), "export16", this::exportAllActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+E"), false); + addMenuItem("/file/export/exportSelected", translate("menu.file.export.selection"), "exportsel16", this::exportSelectedActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + finishMenu("/file/export"); + + addMenuItem("/file/import", translate("menu.import"), null, null, 0, null, false, null, false); + addMenuItem("/file/import/importXml", translate("menu.file.import.xml"), "importxml32", this::importXmlActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("/file/import/importText", translate("menu.file.import.text"), "importtext32", this::importTextActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/import/importScript", translate("menu.file.import.script"), "importscript32", this::importScriptActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/import/importSymbolClass", translate("menu.file.import.symbolClass"), "importsymbolclass32", this::importSymbolClassActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + finishMenu("/file/import"); + + addMenuItem("/file/start", translate("menu.file.start"), null, null, 0, null, false, null, false); + addMenuItem("/file/start/run", translate("menu.file.start.run"), "play32", this::runActionPerformed, PRIORITY_TOP, null, true, new HotKey("F6"), false); + addMenuItem("/file/start/debug", translate("menu.file.start.debug"), "debug32", this::debugActionPerformed, PRIORITY_TOP, null, true, new HotKey("CTRL+F5"), false); + addMenuItem("/file/start/stop", translate("menu.file.start.stop"), "stop32", this::stopActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("/file/start/debugpcode", translate("menu.file.start.debugpcode"), "debug32", this::debugPCodeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + finishMenu("/file/start"); + + addMenuItem("/file/view", translate("menu.view"), null, null, 0, null, false, null, false); + addToggleMenuItem("/file/view/viewResources", translate("menu.file.view.resources"), "view", "viewresources16", this::viewResourcesActionPerformed, PRIORITY_MEDIUM, null); + addToggleMenuItem("/file/view/viewHex", translate("menu.file.view.hex"), "view", "viewhex16", this::viewHexActionPerformed, PRIORITY_MEDIUM, null); + finishMenu("/file/view"); + + addSeparator("/file"); + addMenuItem("/file/close", translate("menu.file.close"), "close32", this::closeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/closeAll", translate("menu.file.closeAll"), "closeall32", this::closeAllActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+X"), false); + + if (!supportsAppMenu()) { + addSeparator("/file"); + addMenuItem("/file/exit", translate("menu.file.exit"), "exit32", this::exitActionPerformed, PRIORITY_TOP, null, true, null, false); + } + + finishMenu("/file"); + + if (Configuration.dumpView.get()) { + setGroupSelection("view", "/file/view/viewHex"); + } else { + setGroupSelection("view", "/file/view/viewResources"); + } + + /* + menu.file.start = Start + menu.file.start.run = Run + menu.file.start.stop = Stop + menu.file.start.debug = Debug + menu.debugging = Debugging + menu.debugging.debug = Debug + menu.debugging.debug.stop = Stop + menu.debugging.debug.pause = Pause + menu.debugging.debug.stepOver = Step over + menu.debugging.debug.stepInto = Step into + menu.debugging.debug.stepOut = Step out + menu.debugging.debug.continue = Continue + menu.debugging.debug.stack = Stack... + menu.debugging.debug.watch = New watch... + */ + addMenuItem("/debugging", translate("menu.debugging"), null, null, 0, null, false, null, true); + addMenuItem("/debugging/debug", translate("menu.debugging.debug"), null, null, 0, null, false, null, false); + addMenuItem("/debugging/debug/stop", translate("menu.file.start.stop"), "stop32", this::stopActionPerformed, PRIORITY_TOP, null, true, null, false); + //addMenuItem("/debugging/debug/pause", translate("menu.debugging.debug.pause"), "pause32", this::pauseActionPerformed, PRIORITY_TOP, null, true,false); + addMenuItem("/debugging/debug/continue", translate("menu.debugging.debug.continue"), "continue32", this::continueActionPerformed, PRIORITY_TOP, null, true, new HotKey("F5"), false); + addMenuItem("/debugging/debug/stepOver", translate("menu.debugging.debug.stepOver"), "stepover32", this::stepOverActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("F8"), false); + addMenuItem("/debugging/debug/stepInto", translate("menu.debugging.debug.stepInto"), "stepinto32", this::stepIntoActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("F7"), false); + addMenuItem("/debugging/debug/stepOut", translate("menu.debugging.debug.stepOut"), "stepout32", this::stepOutActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+F7"), false); + //addMenuItem("/debugging/debug/stack", translate("menu.debugging.debug.stack"), "stack32", this::stackActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + //addMenuItem("/debugging/debug/watch", translate("menu.debugging.debug.watch"), "watch32", this::watchActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + finishMenu("/debugging/debug"); + finishMenu("/debugging"); + + addMenuItem("/tools", translate("menu.tools"), null, null, 0, null, false, null, false); + addMenuItem("/tools/search", translate("menu.tools.search"), "search16", this::searchActionPerformed, PRIORITY_TOP, null, true, null, false); + + addMenuItem("/tools/replace", translate("menu.tools.replace"), "replace32", this::replaceActionPerformed, PRIORITY_TOP, null, true, null, false); + addToggleMenuItem("/tools/timeline", translate("menu.tools.timeline"), null, "timeline32", this::timelineActionPerformed, PRIORITY_TOP, null); + + addMenuItem("/tools/showProxy", translate("menu.tools.proxy"), "proxy16", this::showProxyActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + if (Platform.isWindows()) { + addMenuItem("/tools/searchMemory", translate("menu.tools.searchMemory"), "loadmemory16", this::searchMemoryActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + } + + //addMenuItem("/tools/searchCache", translate("menu.tools.searchCache"), "loadcache16", this::searchCacheActionPerformed, PRIORITY_MEDIUM, null, true, null); + addMenuItem("/tools/deobfuscation", translate("menu.tools.deobfuscation"), "deobfuscate16", null, 0, null, false, null, false); + addMenuItem("/tools/deobfuscation/renameOneIdentifier", translate("menu.tools.deobfuscation.globalrename"), "rename16", this::renameOneIdentifier, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/tools/deobfuscation/renameInvalidIdentifiers", translate("menu.tools.deobfuscation.renameinvalid"), "renameall16", this::renameInvalidIdentifiers, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/tools/deobfuscation/deobfuscation", translate("menu.tools.deobfuscation.pcode"), "deobfuscate32", this::deobfuscationActionPerformed, PRIORITY_TOP, null, true, null, false); + finishMenu("/tools/deobfuscation"); + + /*addMenuItem("/tools/debugger", translate("menu.debugger"), null, null, 0, null, false, null,false); + addToggleMenuItem("/tools/debugger/debuggerSwitch", translate("menu.debugger.switch"), null, "debugger32", this::debuggerSwitchActionPerformed, PRIORITY_TOP, null,false); + addMenuItem("/tools/debugger/debuggerReplaceTrace", translate("menu.debugger.replacetrace"), "debuggerreplace16", this::debuggerReplaceTraceCallsActionPerformed, PRIORITY_MEDIUM, null, true, null,false); + //addMenuItem("/tools/debugger/debuggerInjectLoader", "Inject Loader", "debuggerreplace16", this::debuggerInjectLoader, PRIORITY_MEDIUM, null, true,false); + addMenuItem("/tools/debugger/debuggerShowLog", translate("menu.debugger.showlog"), "debuggerlog16", this::debuggerShowLogActionPerformed, PRIORITY_MEDIUM, null, true, null,false); + finishMenu("/tools/debugger");*/ + addMenuItem("/tools/gotoDocumentClass", translate("menu.tools.gotoDocumentClass"), "gotomainclass32", this::gotoDucumentClassActionPerformed, PRIORITY_TOP, null, true, null, false); + finishMenu("/tools"); + + //Settings + addMenuItem("/settings", translate("menu.settings"), null, null, 0, null, false, null, false); + + addToggleMenuItem("/settings/autoDeobfuscation", translate("menu.settings.autodeobfuscation"), null, null, this::autoDeobfuscationActionPerformed, 0, null); + addToggleMenuItem("/settings/simplifyExpressions", translate("menu.settings.simplifyExpressions"), null, null, this::simplifyExpressionsActionPerformed, 0, null); + addToggleMenuItem("/settings/internalViewer", translate("menu.settings.internalflashviewer"), null, null, this::internalViewerSwitchActionPerformed, 0, null); + addToggleMenuItem("/settings/parallelSpeedUp", translate("menu.settings.parallelspeedup"), null, null, this::parallelSpeedUpActionPerformed, 0, null); + addToggleMenuItem("/settings/disableDecompilation", translate("menu.settings.disabledecompilation"), null, null, this::disableDecompilationActionPerformed, 0, null); + //addToggleMenuItem("/settings/cacheOnDisk", translate("menu.settings.cacheOnDisk"), null, null, this::cacheOnDiskActionPerformed, 0, null); + addToggleMenuItem("/settings/gotoMainClassOnStartup", translate("menu.settings.gotoMainClassOnStartup"), null, null, this::gotoDucumentClassOnStartupActionPerformed, 0, null); + addToggleMenuItem("/settings/autoRenameIdentifiers", translate("menu.settings.autoRenameIdentifiers"), null, null, this::autoRenameIdentifiersActionPerformed, 0, null); + addToggleMenuItem("/settings/autoOpenLoadedSWFs", translate("menu.settings.autoOpenLoadedSWFs"), null, null, this::autoOpenLoadedSWFsActionPerformed, 0, null); + if (Platform.isWindows()) { + addToggleMenuItem("/settings/associate", translate("menu.settings.addtocontextmenu"), null, null, this::associateActionPerformed, 0, null); + } + + addMenuItem("/settings/language", translate("menu.language"), null, null, 0, null, false, null, false); + addMenuItem("/settings/language/setLanguage", translate("menu.settings.language"), "setlanguage32", this::setLanguageActionPerformed, PRIORITY_TOP, null, true, null, false); + finishMenu("/settings/language"); + + /*addMenuItem("/settings/deobfuscation", translate("menu.deobfuscation"), null, null, 0, null, false,false); + addToggleMenuItem("/settings/deobfuscation/old", translate("menu.file.deobfuscation.old"), "deobfuscation", "deobfuscateold16", (ActionEvent e) -> { + deobfuscationMode(e, 0); + }, 0); + addToggleMenuItem("/settings/deobfuscation/new", translate("menu.file.deobfuscation.new"), "deobfuscation", "deobfuscatenew16", (ActionEvent e) -> { + deobfuscationMode(e, 1); + }, 0); + + finishMenu("/settings/deobfuscation");*/ + addMenuItem("/settings/advancedSettings", translate("menu.advancedsettings.advancedsettings"), null, null, 0, null, false, null, false); + addMenuItem("/settings/advancedSettings/advancedSettings", translate("menu.advancedsettings.advancedsettings"), "settings32", this::advancedSettingsActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("/settings/advancedSettings/clearRecentFiles", translate("menu.tools.otherTools.clearRecentFiles"), "clearrecent16", this::clearRecentFilesActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + finishMenu("/settings/advancedSettings"); + + finishMenu("/settings"); + + setMenuChecked("/settings/autoDeobfuscation", Configuration.autoDeobfuscate.get()); + Configuration.autoDeobfuscate.addListener(configListenerAutoDeobfuscate = (Boolean newValue) -> { + setMenuChecked("/settings/autoDeobfuscation", newValue); + }); + + setMenuChecked("/settings/simplifyExpressions", Configuration.simplifyExpressions.get()); + Configuration.simplifyExpressions.addListener(configListenerSimplifyExpressions = (Boolean newValue) -> { + setMenuChecked("/settings/simplifyExpressions", newValue); + }); + + setMenuChecked("/settings/internalViewer", Configuration.internalFlashViewer.get() || externalFlashPlayerUnavailable); + Configuration.internalFlashViewer.addListener(configListenerInternalFlashViewer = (Boolean newValue) -> { + setMenuChecked("/settings/internalViewer", newValue || externalFlashPlayerUnavailable); + }); + + setMenuChecked("/settings/parallelSpeedUp", Configuration.parallelSpeedUp.get()); + Configuration.parallelSpeedUp.addListener(configListenerParallelSpeedUp = (Boolean newValue) -> { + setMenuChecked("/settings/parallelSpeedUp", newValue); + }); + + setMenuChecked("/settings/disableDecompilation", !Configuration.decompile.get()); + Configuration.decompile.addListener(configListenerDecompile = (Boolean newValue) -> { + setMenuChecked("/settings/disableDecompilation", !newValue); + }); + + /*setMenuChecked("/settings/cacheOnDisk", Configuration.cacheOnDisk.get()); + Configuration.cacheOnDisk.addListener(configListenerCacheOnDisk = (Boolean newValue) -> { + setMenuChecked("/settings/cacheOnDisk", newValue); + });*/ + setMenuChecked("/settings/gotoMainClassOnStartup", Configuration.gotoMainClassOnStartup.get()); + Configuration.gotoMainClassOnStartup.addListener(configListenerGotoMainClassOnStartup = (Boolean newValue) -> { + setMenuChecked("/settings/gotoMainClassOnStartup", newValue); + }); + + setMenuChecked("/settings/autoRenameIdentifiers", Configuration.autoRenameIdentifiers.get()); + Configuration.autoRenameIdentifiers.addListener(configListenerAutoRenameIdentifiers = (Boolean newValue) -> { + setMenuChecked("/settings/autoRenameIdentifiers", newValue); + }); + + setMenuChecked("/settings/autoOpenLoadedSWFs", Configuration.autoOpenLoadedSWFs.get()); + Configuration.autoOpenLoadedSWFs.addListener(configListenerAutoOpenLoadedSWFs = (Boolean newValue) -> { + setMenuChecked("/settings/autoOpenLoadedSWFs", newValue); + }); + + if (externalFlashPlayerUnavailable) { + setMenuEnabled("/settings/internalViewer", false); + setMenuEnabled("/settings/autoOpenLoadedSWFs", false); + } + + /*int deobfuscationMode = Configuration.deobfuscationMode.get(); + switch (deobfuscationMode) { + case 0: + setGroupSelection("deobfuscation", "/settings/deobfuscation/old"); + break; + case 1: + setGroupSelection("deobfuscation", "/settings/deobfuscation/new"); + break; + }*/ + if (Platform.isWindows()) { + setMenuChecked("/settings/associate", ContextMenuTools.isAddedToContextMenu()); + } + + //Help + addMenuItem("/help", translate("menu.help"), null, null, 0, null, false, null, false); + addMenuItem("/help/helpUs", translate("menu.help.helpus"), "donate32", this::helpUsActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("/help/homePage", translate("menu.help.homepage"), "homepage16", this::homePageActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addSeparator("/help"); + addMenuItem("/help/checkUpdates", translate("menu.help.checkupdates"), "update16", this::checkUpdatesActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/help/about", translate("menu.help.about"), "about32", this::aboutActionPerformed, PRIORITY_TOP, null, true, null, false); + finishMenu("/help"); + + if (Configuration._showDebugMenu.get() || Configuration._debugMode.get()) { + + addMenuItem("/debug", "# FFDec Debug #", null, null, 0, null, false, null, false); + addMenuItem("/debug/removeNonScripts", "Remove non scripts", "continue16", e -> removeNonScripts(), PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/debug/removeExceptSelected", "Remove except selected", "continue16", e -> removeExceptSelected(), PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/debug/refreshDecompiled", "Refresh decompiled script", "continue16", e -> refreshDecompiled(), PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/debug/checkResources", "Check resources", "continue16", e -> checkResources(), PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/debug/callGc", "Call System.gc()", "continue16", e -> System.gc(), PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/debug/emptyCache", "Empty cache", "continue16", e -> { + SWF nswf = mainFrame.getPanel().getCurrentSwf(); + if (nswf != null) { + nswf.clearAllCache(); + } + }, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/debug/memoryInformation", "Memory information", "continue16", e -> { + String architecture = System.getProperty("sun.arch.data.model"); + Runtime runtime = Runtime.getRuntime(); + String info = "Architecture: " + architecture + Helper.newLine + + "Jre 64bit: " + Helper.is64BitJre() + Helper.newLine + + "Os 64bit: " + Helper.is64BitOs() + Helper.newLine + + "Max: " + (runtime.maxMemory() / 1024 / 1024) + "MB" + Helper.newLine + + "Used: " + (runtime.totalMemory() / 1024 / 1024) + "MB" + Helper.newLine + + "Free: " + (runtime.freeMemory() / 1024 / 1024) + "MB"; + View.showMessageDialog(null, info); + SWF nswf = mainFrame.getPanel().getCurrentSwf(); + if (nswf != null) { + nswf.clearAllCache(); + } + }, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/debug/fixAs3Code", "Fix AS3 code", "continue16", e -> { + SWF nswf = mainFrame.getPanel().getCurrentSwf(); + if (nswf != null) { + nswf.fixAS3Code(); + } + }, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/debug/openTestSwfs", "Open test SWFs", "continue16", e -> { + String path; + + SWFSourceInfo[] sourceInfos = new SWFSourceInfo[2]; + String mainPath = Main.class.getProtectionDomain().getCodeSource().getLocation().getPath(); + path = mainPath + "\\..\\..\\libsrc\\ffdec_lib\\testdata\\as2\\as2.swf"; + sourceInfos[0] = new SWFSourceInfo(null, path, null); + path = mainPath + "\\..\\..\\libsrc\\ffdec_lib\\testdata\\as3\\as3.swf"; + sourceInfos[1] = new SWFSourceInfo(null, path, null); + Main.openFile(sourceInfos); + }, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/debug/createNewSwf", "Create new SWF", "continue16", e -> { + SWF swf = new SWF(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + swf.saveTo(baos); + } catch (IOException ex) { + Logger.getLogger(MainFrameMenu.class.getName()).log(Level.SEVERE, null, ex); + } + + Main.openFile(new SWFSourceInfo(new ByteArrayInputStream(baos.toByteArray()), "New SWF", "New SWF")); + }, PRIORITY_MEDIUM, null, true, null, false); + finishMenu("/debug"); + } + + finishMenu(""); + } + + public void showResourcesView() { + viewResourcesActionPerformed(null); + } + + private void viewResourcesActionPerformed(ActionEvent evt) { + Configuration.dumpView.set(false); + mainFrame.getPanel().showView(MainPanel.VIEW_RESOURCES); + setGroupSelection("view", "/file/view/viewResources"); + setMenuChecked("/tools/timeline", false); + } + + private void viewHexActionPerformed(ActionEvent evt) { + Configuration.dumpView.set(true); + MainPanel mainPanel = mainFrame.getPanel(); + if (mainPanel.isModified()) { + View.showMessageDialog(null, translate("message.warning.hexViewNotUpToDate"), translate("message.warning"), JOptionPane.WARNING_MESSAGE, Configuration.warningHexViewNotUpToDate); + } + + mainPanel.showView(MainPanel.VIEW_DUMP); + setGroupSelection("view", "/file/view/viewHex"); + setMenuChecked("/tools/timeline", false); + } + + private void debuggerSwitchActionPerformed(ActionEvent evt) { + boolean debuggerOn = isMenuChecked("/tools/debugger/debuggerSwitch"); + if (!debuggerOn || View.showConfirmDialog((Component) mainFrame, translate("message.debugger"), translate("dialog.message.title"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE, Configuration.displayDebuggerInfo, JOptionPane.OK_OPTION) == JOptionPane.OK_OPTION) { + switchDebugger(); + mainFrame.getPanel().refreshDecompiled(); + } else { + setMenuChecked("/tools/debugger/debuggerSwitch", false); + } + setMenuEnabled("/tools/debugger/debuggerReplaceTrace", isMenuChecked("/tools/debugger/debuggerSwitch")); + //setMenuEnabled("/tools/debugger/debuggerInjectLoader", isMenuChecked("/tools/debugger/debuggerSwitch")); + } + + private void timelineActionPerformed(ActionEvent evt) { + if (isMenuChecked("/tools/timeline")) { + if (!mainFrame.getPanel().showView(MainPanel.VIEW_TIMELINE)) { + setMenuChecked("/tools/timeline", false); + } else { + setGroupSelection("view", null); + } + } else if (Configuration.dumpView.get()) { + setGroupSelection("view", "/file/view/viewHex"); + mainFrame.getPanel().showView(MainPanel.VIEW_DUMP); + } else { + setGroupSelection("view", "/file/view/viewResources"); + mainFrame.getPanel().showView(MainPanel.VIEW_RESOURCES); + } + } + + protected void loadRecent(ActionEvent evt) { + List recentFiles = Configuration.getRecentFiles(); + clearMenu("/file/" + (supportsMenuAction() ? "open" : "recent")); + clearMenu("_/open"); + + for (int i = recentFiles.size() - 1; i >= 0; i--) { + final String f = recentFiles.get(i); + ActionListener a = (ActionEvent e) -> { + if (Main.openFile(f, null) == OpenFileResult.NOT_FOUND) { + if (View.showConfirmDialog(null, translate("message.confirm.recentFileNotFound"), translate("message.confirm"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_NO_OPTION) { + Configuration.removeRecentFile(f); + } + } + }; + addMenuItem("/file/" + (supportsMenuAction() ? "open" : "recent") + "/" + i, f, null, a, 0, null, true, null, false); + addMenuItem("_/open/" + i, f, null, a, 0, null, true, null, false); + } + + finishMenu("/file/" + (supportsMenuAction() ? "open" : "recent")); + finishMenu("_/open"); + } + + public void dispose() { + KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + manager.removeKeyEventDispatcher(keyEventDispatcher); + + Configuration.autoDeobfuscate.removeListener(configListenerAutoDeobfuscate); + Configuration.simplifyExpressions.removeListener(configListenerSimplifyExpressions); + Configuration.internalFlashViewer.removeListener(configListenerInternalFlashViewer); + Configuration.parallelSpeedUp.removeListener(configListenerParallelSpeedUp); + Configuration.decompile.removeListener(configListenerDecompile); + //Configuration.cacheOnDisk.removeListener(configListenerCacheOnDisk); + Configuration.gotoMainClassOnStartup.removeListener(configListenerGotoMainClassOnStartup); + Configuration.autoRenameIdentifiers.removeListener(configListenerAutoRenameIdentifiers); + Configuration.autoOpenLoadedSWFs.removeListener(configListenerAutoOpenLoadedSWFs); + + Main.stopRun(); + } + + public boolean runActionPerformed(ActionEvent evt) { + Main.run(swf); + return true; + } + + public boolean debugActionPerformed(ActionEvent evt) { + Main.runDebug(swf, false); + return true; + } + + public boolean debugPCodeActionPerformed(ActionEvent evt) { + Main.runDebug(swf, true); + return true; + } + + public boolean stopActionPerformed(ActionEvent evt) { + Main.stopRun(); + return true; + } + + public boolean pauseActionPerformed(ActionEvent evt) { + try { + DebuggerCommands cmd = Main.getDebugHandler().getCommands(); + //TODO + + } catch (IOException ex) { + Main.getDebugHandler().disconnect(); + //ignore + } + return true; + } + + public boolean stepOverActionPerformed(ActionEvent evt) { + + try { + + DebuggerCommands cmd = Main.getDebugHandler().getCommands(); + mainFrame.getPanel().clearDebuggerColors(); + Main.startWork(AppStrings.translate("work.debugging") + "...", null); + + cmd.stepOver(); + } catch (IOException ex) { + Main.getDebugHandler().disconnect(); + //ignore + } + return true; + } + + public boolean stepIntoActionPerformed(ActionEvent evt) { + try { + DebuggerCommands cmd = Main.getDebugHandler().getCommands(); + mainFrame.getPanel().clearDebuggerColors(); + Main.startWork(AppStrings.translate("work.debugging") + "...", null); + + cmd.stepInto(); + } catch (IOException ex) { + Main.getDebugHandler().disconnect(); + //ignore + } + + return true; + } + + public boolean stepOutActionPerformed(ActionEvent evt) { + try { + DebuggerCommands cmd = Main.getDebugHandler().getCommands(); + mainFrame.getPanel().clearDebuggerColors(); + Main.startWork(AppStrings.translate("work.debugging") + "...", null); + cmd.stepOut(); + } catch (IOException ex) { + Main.getDebugHandler().disconnect(); + //ignore + } + + return true; + } + + public boolean continueActionPerformed(ActionEvent evt) { + try { + DebuggerCommands cmd = Main.getDebugHandler().getCommands(); + mainFrame.getPanel().clearDebuggerColors(); + Main.startWork(AppStrings.translate("work.debugging") + "...", null); + cmd.sendContinue(); + } catch (IOException ex) { + Main.getDebugHandler().disconnect(); + //ignore + } + + return true; + } + + public boolean stackActionPerformed(ActionEvent evt) { + //TODO + return true; + } + + public boolean watchActionPerformed(ActionEvent evt) { + //TODO + return true; + } + + public boolean dispatchKeyEvent(KeyEvent e) { + if (((JFrame) mainFrame).isActive() && e.getID() == KeyEvent.KEY_PRESSED) { + + HotKey ek = new HotKey(e); + for (String path : menuHotkeys.keySet()) { + HotKey mk = menuHotkeys.get(path); + if (ek.equals(mk)) { + if (menuActions.containsKey(path)) { + menuActions.get(path).actionPerformed(null); + return true; + } + } + } + + //other nonmenu actions + int code = e.getKeyCode(); + if (e.isControlDown() && e.isShiftDown()) { //CTRL+SHIFT + switch (code) { + case KeyEvent.VK_F: + return searchInActionPerformed(null); + case KeyEvent.VK_T: + return searchInTextPerformed(null); + case KeyEvent.VK_D: + return clearLog(null); + } + } else if (e.isControlDown() && !e.isShiftDown()) { //CTRL + switch (code) { + case KeyEvent.VK_UP: + return previousTag(null); + case KeyEvent.VK_DOWN: + return nextTag(null); + } + } + } + + return false; + } + + public abstract void hilightPath(String path); + + public abstract void setPathVisible(String path, boolean val); +} diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java index 08ca06ffe..c6343785b 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java @@ -1,649 +1,649 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui; - -import com.jpexs.decompiler.flash.configuration.Configuration; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JToggleButton; -import javax.swing.SwingUtilities; -import org.pushingpixels.flamingo.api.common.AbstractCommandButton; -import org.pushingpixels.flamingo.api.common.CommandButtonDisplayState; -import org.pushingpixels.flamingo.api.common.CommandToggleButtonGroup; -import org.pushingpixels.flamingo.api.common.JCommandButton; -import org.pushingpixels.flamingo.api.common.JCommandButtonPanel; -import org.pushingpixels.flamingo.api.common.JCommandToggleButton; -import org.pushingpixels.flamingo.api.common.popup.JPopupPanel; -import org.pushingpixels.flamingo.api.common.popup.PopupPanelCallback; -import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand; -import org.pushingpixels.flamingo.api.ribbon.JRibbon; -import org.pushingpixels.flamingo.api.ribbon.JRibbonBand; -import org.pushingpixels.flamingo.api.ribbon.JRibbonComponent; -import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenu; -import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntryFooter; -import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntryPrimary; -import org.pushingpixels.flamingo.api.ribbon.RibbonContextualTaskGroup; -import org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority; -import org.pushingpixels.flamingo.api.ribbon.RibbonTask; -import org.pushingpixels.flamingo.api.ribbon.resize.BaseRibbonBandResizePolicy; -import org.pushingpixels.flamingo.api.ribbon.resize.CoreRibbonResizePolicies; -import org.pushingpixels.flamingo.api.ribbon.resize.IconRibbonBandResizePolicy; -import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizePolicy; -import org.pushingpixels.flamingo.internal.ui.ribbon.AbstractBandControlPanel; - -/** - * - * @author JPEXS - */ -public class MainFrameRibbonMenu extends MainFrameMenu { - - private final JRibbon ribbon; - - private final Map menuItems = new HashMap<>(); - - private final Map menuTitles = new HashMap<>(); - - private final Map menuOptional = new HashMap<>(); - - private final Map menuIcons = new HashMap<>(); - - private final Map menuLoaders = new HashMap<>(); - - private final Map menuPriorities = new HashMap<>(); - - private final Map> menuSubs = new HashMap<>(); - - private final Map menuType = new HashMap<>(); - - private final Map menuGroup = new HashMap<>(); - - private final Map menuToggleGroups = new HashMap<>(); - - private final Map menuToToggleGroup = new HashMap<>(); - - private static final int TYPE_MENU = 1; - - private static final int TYPE_MENUITEM = 2; - - private static final int TYPE_TOGGLEMENUITEM = 3; - - private final Map optionalGroups = new HashMap<>(); - - public MainFrameRibbonMenu(MainFrameRibbon mainFrame, JRibbon ribbon, boolean externalFlashPlayerUnavailable) { - super(mainFrame, externalFlashPlayerUnavailable); - this.ribbon = ribbon; - } - - private String fixCommandTitle(String title) { - if (title.length() > 2) { - if (title.charAt(1) == ' ') { - title = title.charAt(0) + "\u00A0" + title.substring(2); - } - } - return title; - } - - private RibbonBandResizePolicy titleResizePolicies(final JRibbonBand ribbonBand) { - return new BaseRibbonBandResizePolicy(ribbonBand.getControlPanel()) { - @Override - public int getPreferredWidth(int i, int i1) { - return ribbonBand.getGraphics().getFontMetrics(ribbonBand.getFont()).stringWidth(ribbonBand.getTitle()) + 20; - } - - @Override - public void install(int i, int i1) { - } - }; - } - - private List getResizePolicies(JRibbonBand ribbonBand) { - final List myResizePolicies = new ArrayList<>(); - myResizePolicies.add(new CoreRibbonResizePolicies.Mirror(ribbonBand.getControlPanel())); - myResizePolicies.add(titleResizePolicies(ribbonBand)); - myResizePolicies.add(new IconRibbonBandResizePolicy(ribbonBand.getControlPanel())); - - List resizePolicies = new ArrayList<>(); - - resizePolicies.add(new RibbonBandResizePolicy() { - - @Override - public int getPreferredWidth(int i, int i1) { - int pw = 0; - for (RibbonBandResizePolicy p : myResizePolicies) { - int npw = p.getPreferredWidth(i, i1); - if (npw > pw) { - pw = npw; - } - } - return pw; - } - - @Override - public void install(int i, int i1) { - for (RibbonBandResizePolicy p : myResizePolicies) { - p.install(i, i1); - } - } - }); - return resizePolicies; - } - - @Override - protected void loadRecent(ActionEvent evt) { - if (evt.getSource() instanceof JPanel) { - JPanel targetPanel = (JPanel) evt.getSource(); - targetPanel.removeAll(); - JCommandButtonPanel openHistoryPanel = new JCommandButtonPanel(CommandButtonDisplayState.MEDIUM); - String groupName = translate("menu.recentFiles"); - openHistoryPanel.addButtonGroup(groupName); - List recentFiles = Configuration.getRecentFiles(); - int j = 0; - for (int i = recentFiles.size() - 1; i >= 0; i--) { - String path = recentFiles.get(i); - RecentFilesButton historyButton = new RecentFilesButton(j + " " + path, null); - historyButton.fileName = path; - historyButton.addActionListener((ActionEvent ae) -> { - RecentFilesButton source = (RecentFilesButton) ae.getSource(); - if (Main.openFile(source.fileName, null) == OpenFileResult.NOT_FOUND) { - if (View.showConfirmDialog(null, translate("message.confirm.recentFileNotFound"), translate("message.confirm"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_NO_OPTION) { - Configuration.removeRecentFile(source.fileName); - } - } - }); - j++; - historyButton.setHorizontalAlignment(SwingUtilities.LEFT); - openHistoryPanel.addButtonToLastGroup(historyButton); - } - - if (recentFiles.isEmpty()) { - JCommandButton emptyLabel = new JCommandButton(translate("menu.recentFiles.empty")); - emptyLabel.setHorizontalAlignment(SwingUtilities.LEFT); - emptyLabel.setEnabled(false); - openHistoryPanel.addButtonToLastGroup(emptyLabel); - } - - openHistoryPanel.setMaxButtonColumns(1); - targetPanel.setLayout(new BorderLayout()); - targetPanel.add(openHistoryPanel, BorderLayout.CENTER); - } - } - - @Override - public void finishMenu(String path) { - if (!menuTitles.containsKey(path)) { - throw new IllegalArgumentException("Menu not started: " + path); - } - boolean isAppMenu = path.equals("_"); - String title = menuTitles.get(path); - String icon = menuIcons.get(path); - ActionListener action = menuActions.get(path); - int priority = menuPriorities.get(path); - int type = menuType.get(path); - if (type != TYPE_MENU) { - throw new IllegalArgumentException("Not a menu: " + path); - } - - String parts[] = path.contains("/") ? path.split("\\/") : new String[]{""}; - List subs = menuSubs.get(path); - - boolean onlyCheckboxes = true; - for (String sub : subs) { - if (sub.equals("-")) { - continue; - } - int subType = menuType.get(sub); - if (subType == TYPE_MENUITEM) { - onlyCheckboxes = false; - break; - } - String subIcon = menuIcons.get(sub); - if (subIcon != null) { - onlyCheckboxes = false; - break; - } - } - if (subs.isEmpty()) { - onlyCheckboxes = false; - } - - if (isAppMenu) { - RibbonApplicationMenu mainMenu = new RibbonApplicationMenu(); - for (String sub : subs) { - if (sub.equals("-")) { - mainMenu.addMenuSeparator(); - continue; - } - - String subTitle = menuTitles.get(sub); - String subIcon = menuIcons.get(sub); - int subType = menuType.get(sub); - ActionListener subAction = menuActions.get(sub); - final ActionListener subLoader = menuLoaders.get(sub); - - if (sub.startsWith("_/$")) //FooterMenu - { - RibbonApplicationMenuEntryFooter footerMenu = new RibbonApplicationMenuEntryFooter(View.getResizableIcon(subIcon, 16), subTitle, subAction); - menuItems.put(sub, footerMenu); - mainMenu.addFooterEntry(footerMenu); - } else { - RibbonApplicationMenuEntryPrimary menu = new RibbonApplicationMenuEntryPrimary(View.getResizableIcon(subIcon, 32), subTitle, subAction, - subType == TYPE_MENU ? JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION : JCommandButton.CommandButtonKind.ACTION_ONLY); - - if (subLoader != null) { - menu.setRolloverCallback(new RibbonApplicationMenuEntryPrimary.PrimaryRolloverCallback() { - @Override - public void menuEntryActivated(JPanel targetPanel) { - subLoader.actionPerformed(new ActionEvent(targetPanel, 0, "load:" + sub)); - } - }); - } - menuItems.put(sub, menu); - mainMenu.addMenuEntry(menu); - } - - } - - ribbon.setApplicationMenu(mainMenu); - return; - } - - for (String sub : subs) { - if (sub.equals("-")) { - continue; - } - int subType = menuType.get(sub); - ActionListener subAction = menuActions.get(sub); - String subTitle = menuTitles.get(sub); - String subIcon = menuIcons.get(sub); - String subGroup = menuGroup.get(sub); - HotKey subKey = menuHotkeys.get(sub); - if (subKey != null) { - String keyStr = subKey.toString(); - if (keyStr.length() < 8) { - subTitle += " (" + keyStr + ")"; - } - } - int subPriority = menuPriorities.get(sub); - final ActionListener subLoader = menuLoaders.get(sub); - AbstractCommandButton but = null; - if (subType == TYPE_MENUITEM || (subType == TYPE_MENU && subAction != null)) { - JCommandButton cbut; - if (subIcon != null) { - cbut = new JCommandButton(fixCommandTitle(subTitle), View.getResizableIcon(subIcon, subPriority == PRIORITY_TOP ? 32 : 16)); - } else { - cbut = new JCommandButton(fixCommandTitle(subTitle)); - } - if (subKey != null) { - //cbut.setActionRichTooltip(new RichTooltip(subTitle, subKey.toString())); - } - if (subLoader != null) { - cbut.setCommandButtonKind(JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION); - cbut.setPopupCallback(new PopupPanelCallback() { - - @Override - public JPopupPanel getPopupPanel(JCommandButton jcb) { - JPopupPanel jp = new JPopupPanel() { - }; - - subLoader.actionPerformed(new ActionEvent(jp, 0, "load:" + sub)); - return jp; - } - }); - } - but = cbut; - } else if (subType == TYPE_TOGGLEMENUITEM) { - if (onlyCheckboxes) { - JCheckBox cb = new JCheckBox(subTitle); - if (subAction != null) { - cb.addActionListener(subAction); - } - menuItems.put(sub, cb); - } else { - if (subIcon != null) { - but = new JCommandToggleButton(fixCommandTitle(subTitle), View.getResizableIcon(subIcon, subPriority == PRIORITY_TOP ? 32 : 16)); - } else { - but = new JCommandToggleButton(fixCommandTitle(subTitle)); - } - menuToToggleGroup.get(sub).add((JCommandToggleButton) but); - } - } - if (but != null) { - if (subAction != null) { - but.addActionListener(subAction); - } - menuItems.put(sub, but); - } - } - - //if (parts.length == 3) - { //3rd level - it's a Band! - JRibbonBand band = new JRibbonBand(title, icon != null ? View.getResizableIcon(icon, 16) : null, null); - band.setResizePolicies(getResizePolicies(band)); - int cnt = 0; - for (String sub : subs) { - if (sub.equals("-")) { - continue; - } - - Object o = menuItems.get(sub); - int subPriority = menuPriorities.get(sub); - int subType = menuType.get(sub); - ActionListener subAction = menuActions.get(sub); - if (subType != TYPE_MENU || (subAction != null)) { - if (o instanceof AbstractCommandButton) { - RibbonElementPriority ribbonPriority = RibbonElementPriority.MEDIUM; - switch (subPriority) { - case PRIORITY_LOW: - ribbonPriority = RibbonElementPriority.LOW; - break; - case PRIORITY_MEDIUM: - ribbonPriority = RibbonElementPriority.MEDIUM; - break; - case PRIORITY_TOP: - ribbonPriority = RibbonElementPriority.TOP; - break; - } - - band.addCommandButton((AbstractCommandButton) o, ribbonPriority); - cnt++; - } else if (o instanceof JComponent) { - band.addRibbonComponent(new JRibbonComponent((JComponent) o)); - cnt++; - } - } - } - if (cnt > 0) { - if (parts.length != 3) { - if (!menuSubs.containsKey(path)) { - menuSubs.put(path, new ArrayList<>()); - } - if (!menuSubs.get(path).contains(path + "/_")) { - menuSubs.get(path).add(0, path + "/_"); - } - menuItems.put(path + "/_", band); - - } else { - menuItems.put(path, band); - } - - } - - } - - if (parts.length == 1) { //1st level - it's ribbon - for (String sub : subs) { - if (sub.equals("-")) { - continue; - } - if (menuItems.get(sub) instanceof RibbonTask) { - RibbonTask rt = (RibbonTask) menuItems.get(sub); - if (menuOptional.get(sub)) { - RibbonContextualTaskGroup rct = new RibbonContextualTaskGroup("", new Color(128, 0, 0), rt); - ribbon.addContextualTaskGroup(rct); - optionalGroups.put(sub, rct); - //ribbon.setVisible(rct, false); - } else { - ribbon.addTask(rt); - } - } - } - } else if (parts.length == 2) { //2nd level - it's a Task! - int bandCount = 0; - for (String sub : subs) { - if (sub.equals("-")) { - continue; - } - - if (menuItems.get(sub) instanceof AbstractRibbonBand) { - bandCount++; - } - } - AbstractRibbonBand bands[] = new AbstractRibbonBand[bandCount]; - int b = 0; - for (String sub : subs) { - if (sub.equals("-")) { - continue; - } - if (menuItems.get(sub) instanceof AbstractRibbonBand) { - bands[b++] = (AbstractRibbonBand) menuItems.get(sub); - } - } - if (bands.length > 0) { - RibbonTask task = new RibbonTask(title, bands); - menuItems.put(path, task); - } - } - } - - @Override - public void addMenuItem(String path, String title, String icon, ActionListener action, int priority, ActionListener subLoader, boolean isLeaf, HotKey key, boolean isOptional) { - String parentPath = path.contains("/") ? path.substring(0, path.lastIndexOf('/')) : ""; - if (!menuSubs.containsKey(parentPath)) { - throw new IllegalArgumentException("No parent menu exists: " + parentPath); - } - menuOptional.put(path, isOptional); - menuHotkeys.put(path, key); - menuSubs.get(parentPath).add(path); - if (!isLeaf) { - menuSubs.put(path, new ArrayList<>()); - } - menuLoaders.put(path, subLoader); - menuTitles.put(path, title); - menuIcons.put(path, icon); - menuActions.put(path, action); - menuPriorities.put(path, priority); - menuType.put(path, isLeaf ? TYPE_MENUITEM : TYPE_MENU); - } - - @Override - public void addToggleMenuItem(String path, String title, String group, String icon, ActionListener action, int priority, HotKey key) { - addMenuItem(path, title, icon, action, priority, action, true, key, false); - menuType.put(path, TYPE_TOGGLEMENUITEM); - menuGroup.put(path, group); - if (group == null) { - group = path; - } - if (!menuToggleGroups.containsKey(group)) { - menuToggleGroups.put(group, new CommandToggleButtonGroup()); - } - menuToToggleGroup.put(path, menuToggleGroups.get(group)); - } - - @Override - public boolean isMenuChecked(String path) { - Object o = menuItems.get(path); - if (o instanceof JCommandToggleButton) { - JCommandToggleButton t = (JCommandToggleButton) o; - if (!menuToToggleGroup.containsKey(path)) { - throw new IllegalArgumentException("No toggle group for " + path); - } - return menuToToggleGroup.get(path).getSelected() == t; - } - if (o instanceof JCheckBox) { - return ((JCheckBox) o).isSelected(); - } - throw new IllegalArgumentException("Not a toggle menu"); - } - - @Override - public void setMenuChecked(String path, boolean checked) { - Object o = menuItems.get(path); - if (o instanceof JCommandToggleButton) { - JCommandToggleButton t = (JCommandToggleButton) o; - if (!menuToToggleGroup.containsKey(path)) { - throw new IllegalArgumentException("No toggle group for " + path); - } - menuToToggleGroup.get(path).setSelected(t, checked); - } else if (o instanceof JToggleButton) { - ((JToggleButton) o).setSelected(checked); - } else { - throw new IllegalArgumentException("Not a toggle menu"); - } - } - - @Override - public void setGroupSelection(String group, String selected) { - if (!menuToggleGroups.containsKey(group)) { - throw new IllegalArgumentException("Group " + group + " does not exist"); - } - menuToggleGroups.get(group).clearSelection(); - if (selected == null) { - return; - } - if (!menuItems.containsKey(selected)) { - throw new IllegalArgumentException("Selection " + selected + " not found"); - } - JCommandToggleButton c = (JCommandToggleButton) menuItems.get(selected); - menuToggleGroups.get(group).setSelected(c, true); - } - - @Override - public String getGroupSelection(String group) { - if (!menuToggleGroups.containsKey(group)) { - throw new IllegalArgumentException("Group " + group + " does not exist"); - } - JCommandToggleButton c = menuToggleGroups.get(group).getSelected(); - for (String path : menuItems.keySet()) { - if (menuItems.get(path) == c) { - return path; - } - } - return null; - } - - @Override - public void clearMenu(String path) { - - } - - @Override - public void setMenuEnabled(String path, boolean enabled) { - if (!menuItems.containsKey(path)) { - throw new IllegalArgumentException("Menu not found: " + path); - } - Object o = menuItems.get(path); - try { - if (o instanceof JRibbonBand) { - ((JRibbonBand) o).setEnabled(enabled); - } else if (o instanceof AbstractCommandButton) { - ((AbstractCommandButton) o).setEnabled(enabled); - } else if (o instanceof RibbonApplicationMenuEntryPrimary) { - ((RibbonApplicationMenuEntryPrimary) o).setEnabled(enabled); - } else if (o instanceof RibbonApplicationMenuEntryFooter) { - ((RibbonApplicationMenuEntryFooter) o).setEnabled(enabled); - } else if (o instanceof JComponent) { - ((JComponent) o).setEnabled(enabled); - } else { - throw new IllegalArgumentException("Cannot set enabled to: " + path); - } - } catch (Exception ex) { - //some substance issues, ignore - } - } - - @Override - public void initMenu() { - menuSubs.put("", new ArrayList<>()); - menuPriorities.put("", 0); - menuActions.put("", null); - menuTitles.put("", null); - menuIcons.put("", null); - menuType.put("", TYPE_MENU); - } - - @Override - public void addSeparator(String parentPath) { - if (!menuSubs.containsKey(parentPath)) { - throw new IllegalArgumentException("Menu does not exist: " + parentPath); - } - menuSubs.get(parentPath).add("-"); - } - - @Override - public boolean supportsMenuAction() { - return true; - } - - @Override - public boolean supportsAppMenu() { - return true; - } - - @Override - public void setPathVisible(String path, boolean val) { - Object o = menuItems.get(path); - if (o instanceof RibbonTask) { - if (menuOptional.get(path)) { - RibbonContextualTaskGroup rg = optionalGroups.get(path); - - if (ribbon.isVisible(rg) != val) { - View.execInEventDispatch(new Runnable() { - - @Override - public void run() { - try { - ribbon.setVisible(rg, val); - } catch (Exception ex) { - - } - } - }); - - } - } - } - } - - @Override - public void hilightPath(String path) { - Object o = menuItems.get(path); - if (o instanceof RibbonTask) { - if (menuOptional.get(path)) { - View.execInEventDispatch(new Runnable() { - - @Override - public void run() { - if (!ribbon.isVisible(optionalGroups.get(path))) { - ribbon.setVisible(optionalGroups.get(path), true); - } - ribbon.setSelectedTask((RibbonTask) o); - } - }); - return; - } - final RibbonTask rt = (RibbonTask) o; - View.execInEventDispatch(new Runnable() { - - @Override - public void run() { - ribbon.setSelectedTask(rt); - } - }); - - } - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JToggleButton; +import javax.swing.SwingUtilities; +import org.pushingpixels.flamingo.api.common.AbstractCommandButton; +import org.pushingpixels.flamingo.api.common.CommandButtonDisplayState; +import org.pushingpixels.flamingo.api.common.CommandToggleButtonGroup; +import org.pushingpixels.flamingo.api.common.JCommandButton; +import org.pushingpixels.flamingo.api.common.JCommandButtonPanel; +import org.pushingpixels.flamingo.api.common.JCommandToggleButton; +import org.pushingpixels.flamingo.api.common.popup.JPopupPanel; +import org.pushingpixels.flamingo.api.common.popup.PopupPanelCallback; +import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand; +import org.pushingpixels.flamingo.api.ribbon.JRibbon; +import org.pushingpixels.flamingo.api.ribbon.JRibbonBand; +import org.pushingpixels.flamingo.api.ribbon.JRibbonComponent; +import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenu; +import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntryFooter; +import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntryPrimary; +import org.pushingpixels.flamingo.api.ribbon.RibbonContextualTaskGroup; +import org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority; +import org.pushingpixels.flamingo.api.ribbon.RibbonTask; +import org.pushingpixels.flamingo.api.ribbon.resize.BaseRibbonBandResizePolicy; +import org.pushingpixels.flamingo.api.ribbon.resize.CoreRibbonResizePolicies; +import org.pushingpixels.flamingo.api.ribbon.resize.IconRibbonBandResizePolicy; +import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizePolicy; +import org.pushingpixels.flamingo.internal.ui.ribbon.AbstractBandControlPanel; + +/** + * + * @author JPEXS + */ +public class MainFrameRibbonMenu extends MainFrameMenu { + + private final JRibbon ribbon; + + private final Map menuItems = new HashMap<>(); + + private final Map menuTitles = new HashMap<>(); + + private final Map menuOptional = new HashMap<>(); + + private final Map menuIcons = new HashMap<>(); + + private final Map menuLoaders = new HashMap<>(); + + private final Map menuPriorities = new HashMap<>(); + + private final Map> menuSubs = new HashMap<>(); + + private final Map menuType = new HashMap<>(); + + private final Map menuGroup = new HashMap<>(); + + private final Map menuToggleGroups = new HashMap<>(); + + private final Map menuToToggleGroup = new HashMap<>(); + + private static final int TYPE_MENU = 1; + + private static final int TYPE_MENUITEM = 2; + + private static final int TYPE_TOGGLEMENUITEM = 3; + + private final Map optionalGroups = new HashMap<>(); + + public MainFrameRibbonMenu(MainFrameRibbon mainFrame, JRibbon ribbon, boolean externalFlashPlayerUnavailable) { + super(mainFrame, externalFlashPlayerUnavailable); + this.ribbon = ribbon; + } + + private String fixCommandTitle(String title) { + if (title.length() > 2) { + if (title.charAt(1) == ' ') { + title = title.charAt(0) + "\u00A0" + title.substring(2); + } + } + return title; + } + + private RibbonBandResizePolicy titleResizePolicies(final JRibbonBand ribbonBand) { + return new BaseRibbonBandResizePolicy(ribbonBand.getControlPanel()) { + @Override + public int getPreferredWidth(int i, int i1) { + return ribbonBand.getGraphics().getFontMetrics(ribbonBand.getFont()).stringWidth(ribbonBand.getTitle()) + 20; + } + + @Override + public void install(int i, int i1) { + } + }; + } + + private List getResizePolicies(JRibbonBand ribbonBand) { + final List myResizePolicies = new ArrayList<>(); + myResizePolicies.add(new CoreRibbonResizePolicies.Mirror(ribbonBand.getControlPanel())); + myResizePolicies.add(titleResizePolicies(ribbonBand)); + myResizePolicies.add(new IconRibbonBandResizePolicy(ribbonBand.getControlPanel())); + + List resizePolicies = new ArrayList<>(); + + resizePolicies.add(new RibbonBandResizePolicy() { + + @Override + public int getPreferredWidth(int i, int i1) { + int pw = 0; + for (RibbonBandResizePolicy p : myResizePolicies) { + int npw = p.getPreferredWidth(i, i1); + if (npw > pw) { + pw = npw; + } + } + return pw; + } + + @Override + public void install(int i, int i1) { + for (RibbonBandResizePolicy p : myResizePolicies) { + p.install(i, i1); + } + } + }); + return resizePolicies; + } + + @Override + protected void loadRecent(ActionEvent evt) { + if (evt.getSource() instanceof JPanel) { + JPanel targetPanel = (JPanel) evt.getSource(); + targetPanel.removeAll(); + JCommandButtonPanel openHistoryPanel = new JCommandButtonPanel(CommandButtonDisplayState.MEDIUM); + String groupName = translate("menu.recentFiles"); + openHistoryPanel.addButtonGroup(groupName); + List recentFiles = Configuration.getRecentFiles(); + int j = 0; + for (int i = recentFiles.size() - 1; i >= 0; i--) { + String path = recentFiles.get(i); + RecentFilesButton historyButton = new RecentFilesButton(j + " " + path, null); + historyButton.fileName = path; + historyButton.addActionListener((ActionEvent ae) -> { + RecentFilesButton source = (RecentFilesButton) ae.getSource(); + if (Main.openFile(source.fileName, null) == OpenFileResult.NOT_FOUND) { + if (View.showConfirmDialog(null, translate("message.confirm.recentFileNotFound"), translate("message.confirm"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_NO_OPTION) { + Configuration.removeRecentFile(source.fileName); + } + } + }); + j++; + historyButton.setHorizontalAlignment(SwingUtilities.LEFT); + openHistoryPanel.addButtonToLastGroup(historyButton); + } + + if (recentFiles.isEmpty()) { + JCommandButton emptyLabel = new JCommandButton(translate("menu.recentFiles.empty")); + emptyLabel.setHorizontalAlignment(SwingUtilities.LEFT); + emptyLabel.setEnabled(false); + openHistoryPanel.addButtonToLastGroup(emptyLabel); + } + + openHistoryPanel.setMaxButtonColumns(1); + targetPanel.setLayout(new BorderLayout()); + targetPanel.add(openHistoryPanel, BorderLayout.CENTER); + } + } + + @Override + public void finishMenu(String path) { + if (!menuTitles.containsKey(path)) { + throw new IllegalArgumentException("Menu not started: " + path); + } + boolean isAppMenu = path.equals("_"); + String title = menuTitles.get(path); + String icon = menuIcons.get(path); + ActionListener action = menuActions.get(path); + int priority = menuPriorities.get(path); + int type = menuType.get(path); + if (type != TYPE_MENU) { + throw new IllegalArgumentException("Not a menu: " + path); + } + + String[] parts = path.contains("/") ? path.split("\\/") : new String[]{""}; + List subs = menuSubs.get(path); + + boolean onlyCheckboxes = true; + for (String sub : subs) { + if (sub.equals("-")) { + continue; + } + int subType = menuType.get(sub); + if (subType == TYPE_MENUITEM) { + onlyCheckboxes = false; + break; + } + String subIcon = menuIcons.get(sub); + if (subIcon != null) { + onlyCheckboxes = false; + break; + } + } + if (subs.isEmpty()) { + onlyCheckboxes = false; + } + + if (isAppMenu) { + RibbonApplicationMenu mainMenu = new RibbonApplicationMenu(); + for (String sub : subs) { + if (sub.equals("-")) { + mainMenu.addMenuSeparator(); + continue; + } + + String subTitle = menuTitles.get(sub); + String subIcon = menuIcons.get(sub); + int subType = menuType.get(sub); + ActionListener subAction = menuActions.get(sub); + final ActionListener subLoader = menuLoaders.get(sub); + + if (sub.startsWith("_/$")) //FooterMenu + { + RibbonApplicationMenuEntryFooter footerMenu = new RibbonApplicationMenuEntryFooter(View.getResizableIcon(subIcon, 16), subTitle, subAction); + menuItems.put(sub, footerMenu); + mainMenu.addFooterEntry(footerMenu); + } else { + RibbonApplicationMenuEntryPrimary menu = new RibbonApplicationMenuEntryPrimary(View.getResizableIcon(subIcon, 32), subTitle, subAction, + subType == TYPE_MENU ? JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION : JCommandButton.CommandButtonKind.ACTION_ONLY); + + if (subLoader != null) { + menu.setRolloverCallback(new RibbonApplicationMenuEntryPrimary.PrimaryRolloverCallback() { + @Override + public void menuEntryActivated(JPanel targetPanel) { + subLoader.actionPerformed(new ActionEvent(targetPanel, 0, "load:" + sub)); + } + }); + } + menuItems.put(sub, menu); + mainMenu.addMenuEntry(menu); + } + + } + + ribbon.setApplicationMenu(mainMenu); + return; + } + + for (String sub : subs) { + if (sub.equals("-")) { + continue; + } + int subType = menuType.get(sub); + ActionListener subAction = menuActions.get(sub); + String subTitle = menuTitles.get(sub); + String subIcon = menuIcons.get(sub); + String subGroup = menuGroup.get(sub); + HotKey subKey = menuHotkeys.get(sub); + if (subKey != null) { + String keyStr = subKey.toString(); + if (keyStr.length() < 8) { + subTitle += " (" + keyStr + ")"; + } + } + int subPriority = menuPriorities.get(sub); + final ActionListener subLoader = menuLoaders.get(sub); + AbstractCommandButton but = null; + if (subType == TYPE_MENUITEM || (subType == TYPE_MENU && subAction != null)) { + JCommandButton cbut; + if (subIcon != null) { + cbut = new JCommandButton(fixCommandTitle(subTitle), View.getResizableIcon(subIcon, subPriority == PRIORITY_TOP ? 32 : 16)); + } else { + cbut = new JCommandButton(fixCommandTitle(subTitle)); + } + if (subKey != null) { + //cbut.setActionRichTooltip(new RichTooltip(subTitle, subKey.toString())); + } + if (subLoader != null) { + cbut.setCommandButtonKind(JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION); + cbut.setPopupCallback(new PopupPanelCallback() { + + @Override + public JPopupPanel getPopupPanel(JCommandButton jcb) { + JPopupPanel jp = new JPopupPanel() { + }; + + subLoader.actionPerformed(new ActionEvent(jp, 0, "load:" + sub)); + return jp; + } + }); + } + but = cbut; + } else if (subType == TYPE_TOGGLEMENUITEM) { + if (onlyCheckboxes) { + JCheckBox cb = new JCheckBox(subTitle); + if (subAction != null) { + cb.addActionListener(subAction); + } + menuItems.put(sub, cb); + } else { + if (subIcon != null) { + but = new JCommandToggleButton(fixCommandTitle(subTitle), View.getResizableIcon(subIcon, subPriority == PRIORITY_TOP ? 32 : 16)); + } else { + but = new JCommandToggleButton(fixCommandTitle(subTitle)); + } + menuToToggleGroup.get(sub).add((JCommandToggleButton) but); + } + } + if (but != null) { + if (subAction != null) { + but.addActionListener(subAction); + } + menuItems.put(sub, but); + } + } + + //if (parts.length == 3) + { //3rd level - it's a Band! + JRibbonBand band = new JRibbonBand(title, icon != null ? View.getResizableIcon(icon, 16) : null, null); + band.setResizePolicies(getResizePolicies(band)); + int cnt = 0; + for (String sub : subs) { + if (sub.equals("-")) { + continue; + } + + Object o = menuItems.get(sub); + int subPriority = menuPriorities.get(sub); + int subType = menuType.get(sub); + ActionListener subAction = menuActions.get(sub); + if (subType != TYPE_MENU || (subAction != null)) { + if (o instanceof AbstractCommandButton) { + RibbonElementPriority ribbonPriority = RibbonElementPriority.MEDIUM; + switch (subPriority) { + case PRIORITY_LOW: + ribbonPriority = RibbonElementPriority.LOW; + break; + case PRIORITY_MEDIUM: + ribbonPriority = RibbonElementPriority.MEDIUM; + break; + case PRIORITY_TOP: + ribbonPriority = RibbonElementPriority.TOP; + break; + } + + band.addCommandButton((AbstractCommandButton) o, ribbonPriority); + cnt++; + } else if (o instanceof JComponent) { + band.addRibbonComponent(new JRibbonComponent((JComponent) o)); + cnt++; + } + } + } + if (cnt > 0) { + if (parts.length != 3) { + if (!menuSubs.containsKey(path)) { + menuSubs.put(path, new ArrayList<>()); + } + if (!menuSubs.get(path).contains(path + "/_")) { + menuSubs.get(path).add(0, path + "/_"); + } + menuItems.put(path + "/_", band); + + } else { + menuItems.put(path, band); + } + + } + + } + + if (parts.length == 1) { //1st level - it's ribbon + for (String sub : subs) { + if (sub.equals("-")) { + continue; + } + if (menuItems.get(sub) instanceof RibbonTask) { + RibbonTask rt = (RibbonTask) menuItems.get(sub); + if (menuOptional.get(sub)) { + RibbonContextualTaskGroup rct = new RibbonContextualTaskGroup("", new Color(128, 0, 0), rt); + ribbon.addContextualTaskGroup(rct); + optionalGroups.put(sub, rct); + //ribbon.setVisible(rct, false); + } else { + ribbon.addTask(rt); + } + } + } + } else if (parts.length == 2) { //2nd level - it's a Task! + int bandCount = 0; + for (String sub : subs) { + if (sub.equals("-")) { + continue; + } + + if (menuItems.get(sub) instanceof AbstractRibbonBand) { + bandCount++; + } + } + AbstractRibbonBand[] bands = new AbstractRibbonBand[bandCount]; + int b = 0; + for (String sub : subs) { + if (sub.equals("-")) { + continue; + } + if (menuItems.get(sub) instanceof AbstractRibbonBand) { + bands[b++] = (AbstractRibbonBand) menuItems.get(sub); + } + } + if (bands.length > 0) { + RibbonTask task = new RibbonTask(title, bands); + menuItems.put(path, task); + } + } + } + + @Override + public void addMenuItem(String path, String title, String icon, ActionListener action, int priority, ActionListener subLoader, boolean isLeaf, HotKey key, boolean isOptional) { + String parentPath = path.contains("/") ? path.substring(0, path.lastIndexOf('/')) : ""; + if (!menuSubs.containsKey(parentPath)) { + throw new IllegalArgumentException("No parent menu exists: " + parentPath); + } + menuOptional.put(path, isOptional); + menuHotkeys.put(path, key); + menuSubs.get(parentPath).add(path); + if (!isLeaf) { + menuSubs.put(path, new ArrayList<>()); + } + menuLoaders.put(path, subLoader); + menuTitles.put(path, title); + menuIcons.put(path, icon); + menuActions.put(path, action); + menuPriorities.put(path, priority); + menuType.put(path, isLeaf ? TYPE_MENUITEM : TYPE_MENU); + } + + @Override + public void addToggleMenuItem(String path, String title, String group, String icon, ActionListener action, int priority, HotKey key) { + addMenuItem(path, title, icon, action, priority, action, true, key, false); + menuType.put(path, TYPE_TOGGLEMENUITEM); + menuGroup.put(path, group); + if (group == null) { + group = path; + } + if (!menuToggleGroups.containsKey(group)) { + menuToggleGroups.put(group, new CommandToggleButtonGroup()); + } + menuToToggleGroup.put(path, menuToggleGroups.get(group)); + } + + @Override + public boolean isMenuChecked(String path) { + Object o = menuItems.get(path); + if (o instanceof JCommandToggleButton) { + JCommandToggleButton t = (JCommandToggleButton) o; + if (!menuToToggleGroup.containsKey(path)) { + throw new IllegalArgumentException("No toggle group for " + path); + } + return menuToToggleGroup.get(path).getSelected() == t; + } + if (o instanceof JCheckBox) { + return ((JCheckBox) o).isSelected(); + } + throw new IllegalArgumentException("Not a toggle menu"); + } + + @Override + public void setMenuChecked(String path, boolean checked) { + Object o = menuItems.get(path); + if (o instanceof JCommandToggleButton) { + JCommandToggleButton t = (JCommandToggleButton) o; + if (!menuToToggleGroup.containsKey(path)) { + throw new IllegalArgumentException("No toggle group for " + path); + } + menuToToggleGroup.get(path).setSelected(t, checked); + } else if (o instanceof JToggleButton) { + ((JToggleButton) o).setSelected(checked); + } else { + throw new IllegalArgumentException("Not a toggle menu"); + } + } + + @Override + public void setGroupSelection(String group, String selected) { + if (!menuToggleGroups.containsKey(group)) { + throw new IllegalArgumentException("Group " + group + " does not exist"); + } + menuToggleGroups.get(group).clearSelection(); + if (selected == null) { + return; + } + if (!menuItems.containsKey(selected)) { + throw new IllegalArgumentException("Selection " + selected + " not found"); + } + JCommandToggleButton c = (JCommandToggleButton) menuItems.get(selected); + menuToggleGroups.get(group).setSelected(c, true); + } + + @Override + public String getGroupSelection(String group) { + if (!menuToggleGroups.containsKey(group)) { + throw new IllegalArgumentException("Group " + group + " does not exist"); + } + JCommandToggleButton c = menuToggleGroups.get(group).getSelected(); + for (String path : menuItems.keySet()) { + if (menuItems.get(path) == c) { + return path; + } + } + return null; + } + + @Override + public void clearMenu(String path) { + + } + + @Override + public void setMenuEnabled(String path, boolean enabled) { + if (!menuItems.containsKey(path)) { + throw new IllegalArgumentException("Menu not found: " + path); + } + Object o = menuItems.get(path); + try { + if (o instanceof JRibbonBand) { + ((JRibbonBand) o).setEnabled(enabled); + } else if (o instanceof AbstractCommandButton) { + ((AbstractCommandButton) o).setEnabled(enabled); + } else if (o instanceof RibbonApplicationMenuEntryPrimary) { + ((RibbonApplicationMenuEntryPrimary) o).setEnabled(enabled); + } else if (o instanceof RibbonApplicationMenuEntryFooter) { + ((RibbonApplicationMenuEntryFooter) o).setEnabled(enabled); + } else if (o instanceof JComponent) { + ((JComponent) o).setEnabled(enabled); + } else { + throw new IllegalArgumentException("Cannot set enabled to: " + path); + } + } catch (Exception ex) { + //some substance issues, ignore + } + } + + @Override + public void initMenu() { + menuSubs.put("", new ArrayList<>()); + menuPriorities.put("", 0); + menuActions.put("", null); + menuTitles.put("", null); + menuIcons.put("", null); + menuType.put("", TYPE_MENU); + } + + @Override + public void addSeparator(String parentPath) { + if (!menuSubs.containsKey(parentPath)) { + throw new IllegalArgumentException("Menu does not exist: " + parentPath); + } + menuSubs.get(parentPath).add("-"); + } + + @Override + public boolean supportsMenuAction() { + return true; + } + + @Override + public boolean supportsAppMenu() { + return true; + } + + @Override + public void setPathVisible(String path, boolean val) { + Object o = menuItems.get(path); + if (o instanceof RibbonTask) { + if (menuOptional.get(path)) { + RibbonContextualTaskGroup rg = optionalGroups.get(path); + + if (ribbon.isVisible(rg) != val) { + View.execInEventDispatch(new Runnable() { + + @Override + public void run() { + try { + ribbon.setVisible(rg, val); + } catch (Exception ex) { + + } + } + }); + + } + } + } + } + + @Override + public void hilightPath(String path) { + Object o = menuItems.get(path); + if (o instanceof RibbonTask) { + if (menuOptional.get(path)) { + View.execInEventDispatch(new Runnable() { + + @Override + public void run() { + if (!ribbon.isVisible(optionalGroups.get(path))) { + ribbon.setVisible(optionalGroups.get(path), true); + } + ribbon.setSelectedTask((RibbonTask) o); + } + }); + return; + } + final RibbonTask rt = (RibbonTask) o; + View.execInEventDispatch(new Runnable() { + + @Override + public void run() { + ribbon.setSelectedTask(rt); + } + }); + + } + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/MenuBuilder.java b/src/com/jpexs/decompiler/flash/gui/MenuBuilder.java index 51ddd2dd3..6443ad35a 100644 --- a/src/com/jpexs/decompiler/flash/gui/MenuBuilder.java +++ b/src/com/jpexs/decompiler/flash/gui/MenuBuilder.java @@ -1,293 +1,293 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui; - -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -/** - * Menu Builder. Creates menu. - * - * @author JPEXS - */ -public interface MenuBuilder { - - public static class HotKey { - - private static Map keyCodesToNames = new HashMap<>(); - - private static Map keyNamesToCodes = new HashMap<>(); - - { - - Field[] fields = KeyEvent.class.getFields(); - - for (int i = 0; i < fields.length; i++) { - - String fieldName = fields[i].getName(); - - // We only care about the field names corresponding to key codes - if (fieldName.startsWith("VK")) { - try { - int keyCode = fields[i].getInt(null); - String keyName = fieldName.substring(3); - keyCodesToNames.put(keyCode, keyName); - keyNamesToCodes.put(keyName, keyCode); - } catch (Exception ex) { - - } - } - } - } - - public int key; - - public boolean shiftDown; - - public boolean ctrlDown; - - public boolean altDown; - - @Override - public int hashCode() { - int hash = 7; - hash = 41 * hash + this.key; - hash = 41 * hash + (this.shiftDown ? 1 : 0); - hash = 41 * hash + (this.ctrlDown ? 1 : 0); - hash = 41 * hash + (this.altDown ? 1 : 0); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final HotKey other = (HotKey) obj; - if (this.key != other.key) { - return false; - } - if (this.shiftDown != other.shiftDown) { - return false; - } - if (this.ctrlDown != other.ctrlDown) { - return false; - } - return (this.altDown == other.altDown); - } - - public boolean matches(KeyEvent ev) { - return ev.getKeyCode() == key && ev.isControlDown() == ctrlDown && ev.isShiftDown() == shiftDown && ev.isAltDown() == altDown; - } - - public int getModifier() { - return (shiftDown ? KeyEvent.SHIFT_MASK : 0) + (ctrlDown ? KeyEvent.CTRL_MASK : 0) + (altDown ? KeyEvent.ALT_MASK : 0); - } - - public HotKey(String h) { - String parts[] = h.contains("+") ? h.split("\\+") : new String[]{h}; - for (String s : parts) { - switch (s) { - case "SHIFT": - shiftDown = true; - break; - case "CTRL": - ctrlDown = true; - break; - case "ALT": - altDown = true; - break; - default: - if (keyNamesToCodes.containsKey(s)) { - key = keyNamesToCodes.get(s); - } else { - throw new IllegalArgumentException("Key " + s + " not found!"); - } - } - } - } - - public HotKey(KeyEvent ev) { - this(ev.getKeyCode(), ev.isShiftDown(), ev.isControlDown(), ev.isAltDown()); - } - - public HotKey(int key) { - this(key, false, false, false); - } - - public HotKey(int key, boolean shiftDown, boolean ctrlDown, boolean altDown) { - this.key = key; - this.shiftDown = shiftDown; - this.ctrlDown = ctrlDown; - this.altDown = altDown; - } - - @Override - public String toString() { - String s = ""; - if (shiftDown) { - s += "SHIFT"; - } - if (ctrlDown) { - if (!s.isEmpty()) { - s += "+"; - } - s += "CTRL"; - } - if (altDown) { - if (!s.isEmpty()) { - s += "+"; - } - s += "ALT"; - } - if (!s.isEmpty()) { - s += "+"; - } - s += keyCodesToNames.get(key); - - return s; - } - } - - public static final int PRIORITY_LOW = 1; - - public static final int PRIORITY_MEDIUM = 2; - - public static final int PRIORITY_TOP = 3; - - /** - * Initializes menuBuilder - */ - public void initMenu(); - - /** - * Adds separator - * - * @param parentPath Parent menu path - */ - public void addSeparator(String parentPath); - - /** - * Adds menu item - * - * @param path Path - * @param title Title - * @param icon Icon - resource name - * @param action Action for clicking - * @param priority Priority - * @param subloader Action which loads menu inside - * @param isLeaf Has no subitems? - * @param key - * @param isOptional - */ - public void addMenuItem(String path, String title, String icon, ActionListener action, int priority, ActionListener subloader, boolean isLeaf, HotKey key, boolean isOptional); - - /** - * Adds toggle item (radio/checkbox) - * - * @param path Path - * @param title Title - * @param group Group for toggling. Null = checkbox - * @param icon Icon - resource name - * @param action Action for clicking - * @param priority Priority - * @param key - */ - public void addToggleMenuItem(String path, String title, String group, String icon, ActionListener action, int priority, HotKey key); - - /** - * Test menu checked (toggle) - * - * @param path Menu path - * @return True when checked - */ - public boolean isMenuChecked(String path); - - /** - * Hotkey for menu - * - * @param path Menu path - * @return - */ - public HotKey getMenuHotkey(String path); - - /** - * Sets menu checked (toggle) - * - * @param path Menu path - * @param checked Checked? - */ - public void setMenuChecked(String path, boolean checked); - - /** - * Sets checked item from group - * - * @param group Group name - * @param selected Selected path - */ - public void setGroupSelection(String group, String selected); - - /** - * Gets selected path from group - * - * @param group Group name - * @return Path - */ - public String getGroupSelection(String group); - - /** - * Clears menu - * - * @param path Path of menu - */ - public void clearMenu(String path); - - /** - * Sets enabled property of menu - * - * @param path Menu path - * @param enabled Enabled? - */ - public void setMenuEnabled(String path, boolean enabled); - - /** - * Finished creating menu - * - * @param path Menu path - */ - public void finishMenu(String path); - - /** - * Does this builder support actions for menus? (not menuitems) - * - * @return True if supports - */ - public boolean supportsMenuAction(); - - /** - * Does this builder support application menu (Path "_/...") - * - * @return True if supports - */ - public boolean supportsAppMenu(); -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +/** + * Menu Builder. Creates menu. + * + * @author JPEXS + */ +public interface MenuBuilder { + + public static class HotKey { + + private static Map keyCodesToNames = new HashMap<>(); + + private static Map keyNamesToCodes = new HashMap<>(); + + { + + Field[] fields = KeyEvent.class.getFields(); + + for (int i = 0; i < fields.length; i++) { + + String fieldName = fields[i].getName(); + + // We only care about the field names corresponding to key codes + if (fieldName.startsWith("VK")) { + try { + int keyCode = fields[i].getInt(null); + String keyName = fieldName.substring(3); + keyCodesToNames.put(keyCode, keyName); + keyNamesToCodes.put(keyName, keyCode); + } catch (Exception ex) { + + } + } + } + } + + public int key; + + public boolean shiftDown; + + public boolean ctrlDown; + + public boolean altDown; + + @Override + public int hashCode() { + int hash = 7; + hash = 41 * hash + this.key; + hash = 41 * hash + (this.shiftDown ? 1 : 0); + hash = 41 * hash + (this.ctrlDown ? 1 : 0); + hash = 41 * hash + (this.altDown ? 1 : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HotKey other = (HotKey) obj; + if (this.key != other.key) { + return false; + } + if (this.shiftDown != other.shiftDown) { + return false; + } + if (this.ctrlDown != other.ctrlDown) { + return false; + } + return (this.altDown == other.altDown); + } + + public boolean matches(KeyEvent ev) { + return ev.getKeyCode() == key && ev.isControlDown() == ctrlDown && ev.isShiftDown() == shiftDown && ev.isAltDown() == altDown; + } + + public int getModifier() { + return (shiftDown ? KeyEvent.SHIFT_MASK : 0) + (ctrlDown ? KeyEvent.CTRL_MASK : 0) + (altDown ? KeyEvent.ALT_MASK : 0); + } + + public HotKey(String h) { + String[] parts = h.contains("+") ? h.split("\\+") : new String[]{h}; + for (String s : parts) { + switch (s) { + case "SHIFT": + shiftDown = true; + break; + case "CTRL": + ctrlDown = true; + break; + case "ALT": + altDown = true; + break; + default: + if (keyNamesToCodes.containsKey(s)) { + key = keyNamesToCodes.get(s); + } else { + throw new IllegalArgumentException("Key " + s + " not found!"); + } + } + } + } + + public HotKey(KeyEvent ev) { + this(ev.getKeyCode(), ev.isShiftDown(), ev.isControlDown(), ev.isAltDown()); + } + + public HotKey(int key) { + this(key, false, false, false); + } + + public HotKey(int key, boolean shiftDown, boolean ctrlDown, boolean altDown) { + this.key = key; + this.shiftDown = shiftDown; + this.ctrlDown = ctrlDown; + this.altDown = altDown; + } + + @Override + public String toString() { + String s = ""; + if (shiftDown) { + s += "SHIFT"; + } + if (ctrlDown) { + if (!s.isEmpty()) { + s += "+"; + } + s += "CTRL"; + } + if (altDown) { + if (!s.isEmpty()) { + s += "+"; + } + s += "ALT"; + } + if (!s.isEmpty()) { + s += "+"; + } + s += keyCodesToNames.get(key); + + return s; + } + } + + public static final int PRIORITY_LOW = 1; + + public static final int PRIORITY_MEDIUM = 2; + + public static final int PRIORITY_TOP = 3; + + /** + * Initializes menuBuilder + */ + public void initMenu(); + + /** + * Adds separator + * + * @param parentPath Parent menu path + */ + public void addSeparator(String parentPath); + + /** + * Adds menu item + * + * @param path Path + * @param title Title + * @param icon Icon - resource name + * @param action Action for clicking + * @param priority Priority + * @param subloader Action which loads menu inside + * @param isLeaf Has no subitems? + * @param key + * @param isOptional + */ + public void addMenuItem(String path, String title, String icon, ActionListener action, int priority, ActionListener subloader, boolean isLeaf, HotKey key, boolean isOptional); + + /** + * Adds toggle item (radio/checkbox) + * + * @param path Path + * @param title Title + * @param group Group for toggling. Null = checkbox + * @param icon Icon - resource name + * @param action Action for clicking + * @param priority Priority + * @param key + */ + public void addToggleMenuItem(String path, String title, String group, String icon, ActionListener action, int priority, HotKey key); + + /** + * Test menu checked (toggle) + * + * @param path Menu path + * @return True when checked + */ + public boolean isMenuChecked(String path); + + /** + * Hotkey for menu + * + * @param path Menu path + * @return + */ + public HotKey getMenuHotkey(String path); + + /** + * Sets menu checked (toggle) + * + * @param path Menu path + * @param checked Checked? + */ + public void setMenuChecked(String path, boolean checked); + + /** + * Sets checked item from group + * + * @param group Group name + * @param selected Selected path + */ + public void setGroupSelection(String group, String selected); + + /** + * Gets selected path from group + * + * @param group Group name + * @return Path + */ + public String getGroupSelection(String group); + + /** + * Clears menu + * + * @param path Path of menu + */ + public void clearMenu(String path); + + /** + * Sets enabled property of menu + * + * @param path Menu path + * @param enabled Enabled? + */ + public void setMenuEnabled(String path, boolean enabled); + + /** + * Finished creating menu + * + * @param path Menu path + */ + public void finishMenu(String path); + + /** + * Does this builder support actions for menus? (not menuitems) + * + * @return True if supports + */ + public boolean supportsMenuAction(); + + /** + * Does this builder support application menu (Path "_/...") + * + * @return True if supports + */ + public boolean supportsAppMenu(); +} diff --git a/src/com/jpexs/decompiler/flash/gui/View.java b/src/com/jpexs/decompiler/flash/gui/View.java index 2f182c3eb..b34802995 100644 --- a/src/com/jpexs/decompiler/flash/gui/View.java +++ b/src/com/jpexs/decompiler/flash/gui/View.java @@ -1,768 +1,768 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui; - -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.configuration.ConfigurationItem; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Desktop; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.GraphicsConfiguration; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.awt.Image; -import java.awt.Rectangle; -import java.awt.SystemColor; -import java.awt.TexturePaint; -import java.awt.Window; -import java.awt.event.ActionEvent; -import java.awt.event.KeyEvent; -import java.awt.event.WindowEvent; -import java.awt.image.BufferedImage; -import java.awt.image.VolatileImage; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.imageio.ImageIO; -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.ActionMap; -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JEditorPane; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JRootPane; -import javax.swing.JTable; -import javax.swing.JTree; -import javax.swing.KeyStroke; -import javax.swing.LookAndFeel; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; -import javax.swing.UIDefaults; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; -import javax.swing.plaf.FontUIResource; -import javax.swing.plaf.basic.BasicColorChooserUI; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.DefaultTableColumnModel; -import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumn; -import javax.swing.table.TableModel; -import javax.swing.text.JTextComponent; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreePath; -import org.pushingpixels.flamingo.api.common.icon.ImageWrapperResizableIcon; -import org.pushingpixels.substance.api.ColorSchemeAssociationKind; -import org.pushingpixels.substance.api.ComponentState; -import org.pushingpixels.substance.api.DecorationAreaType; -import org.pushingpixels.substance.api.SubstanceColorScheme; -import org.pushingpixels.substance.api.SubstanceConstants; -import org.pushingpixels.substance.api.SubstanceLookAndFeel; -import org.pushingpixels.substance.api.SubstanceSkin; -import org.pushingpixels.substance.api.fonts.FontPolicy; -import org.pushingpixels.substance.api.fonts.FontSet; -import org.pushingpixels.substance.api.skin.SubstanceOfficeBlue2007LookAndFeel; -import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities; - -/** - * Contains methods for GUI - * - * @author JPEXS - */ -public class View { - - public static Color getDefaultBackgroundColor() { - if (Configuration.useRibbonInterface.get()) { - return SubstanceLookAndFeel.getCurrentSkin().getColorScheme(DecorationAreaType.GENERAL, ColorSchemeAssociationKind.FILL, ComponentState.ENABLED).getBackgroundFillColor(); - } else { - return SystemColor.control; - } - } - - private static Color swfBackgroundColor = null; - - public static void setSwfBackgroundColor(Color swfBackgroundColor) { - View.swfBackgroundColor = swfBackgroundColor; - } - - public static Color getSwfBackgroundColor() { - if (swfBackgroundColor == null) { - return getDefaultBackgroundColor(); - } - return swfBackgroundColor; - } - - private static final BufferedImage transparentTexture; - - public static final TexturePaint transparentPaint; - - private static final Color transparentColor1 = new Color(0x99, 0x99, 0x99); - - private static final Color transparentColor2 = new Color(0x66, 0x66, 0x66); - - static { - transparentTexture = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); - Graphics g = transparentTexture.getGraphics(); - g.setColor(transparentColor1); - g.fillRect(0, 0, 16, 16); - g.setColor(transparentColor2); - g.fillRect(0, 0, 8, 8); - g.fillRect(8, 8, 8, 8); - transparentPaint = new TexturePaint(View.transparentTexture, new Rectangle(0, 0, transparentTexture.getWidth(), transparentTexture.getHeight())); - } - - /** - * Sets windows Look and Feel - */ - public static void setLookAndFeel() { - - //Save default font for Chinese characters - final Font defaultFont = (new JLabel()).getFont(); - try { - - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - - } catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) { - } - - try { - LookAndFeel oldLookAndFeel = UIManager.getLookAndFeel(); - if (!(oldLookAndFeel instanceof SubstanceOfficeBlue2007LookAndFeel)) { - UIManager.setLookAndFeel(new SubstanceOfficeBlue2007LookAndFeel()); - oldLookAndFeel.uninitialize(); - } - - SubstanceSkin currentSkin = SubstanceLookAndFeel.getCurrentSkin(); - if (currentSkin != null) { - String currentSkinName = currentSkin.getClass().getName(); - String newSkinName = Configuration.guiSkin.get(); - if (!currentSkinName.equals(newSkinName)) { - SubstanceLookAndFeel.setSkin(newSkinName); - } - } else { - Logger.getLogger(View.class.getName()).log(Level.SEVERE, "Current skin is null"); - SubstanceLookAndFeel.setSkin("com.jpexs.decompiler.flash.gui.OceanicSkin"); - } - - UIManager.put(SubstanceLookAndFeel.COLORIZATION_FACTOR, 0.999);//This works for not changing labels color and not changing Dialogs title - UIManager.put("Tree.expandedIcon", getIcon("expand16")); - UIManager.put("Tree.collapsedIcon", getIcon("collapse16")); - UIManager.put("ColorChooserUI", BasicColorChooserUI.class.getName()); - UIManager.put("ColorChooser.swatchesRecentSwatchSize", new Dimension(20, 20)); - UIManager.put("ColorChooser.swatchesSwatchSize", new Dimension(20, 20)); - UIManager.put("RibbonApplicationMenuPopupPanelUI", MyRibbonApplicationMenuPopupPanelUI.class.getName()); - UIManager.put("RibbonApplicationMenuButtonUI", MyRibbonApplicationMenuButtonUI.class.getName()); - UIManager.put("ProgressBarUI", MyProgressBarUI.class.getName()); - UIManager.put("TextField.background", Color.white); - UIManager.put("FormattedTextField.background", Color.white); - UIManager.put("CommandButtonUI", MyCommandButtonUI.class.getName()); - - FontPolicy pol = SubstanceLookAndFeel.getFontPolicy(); - final FontSet fs = pol.getFontSet("Substance", null); - - //Restore default font for chinese characters - SubstanceLookAndFeel.setFontPolicy(new FontPolicy() { - - private final FontSet fontSet = new FontSet() { - - private FontUIResource controlFont; - - private FontUIResource menuFont; - - private FontUIResource titleFont; - - private FontUIResource windowTitleFont; - - private FontUIResource smallFont; - - private FontUIResource messageFont; - - @Override - public FontUIResource getControlFont() { - if (controlFont == null) { - FontUIResource f = fs.getControlFont(); - controlFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); - } - return controlFont; - } - - @Override - public FontUIResource getMenuFont() { - if (menuFont == null) { - FontUIResource f = fs.getMenuFont(); - menuFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); - } - return menuFont; - } - - @Override - public FontUIResource getTitleFont() { - if (titleFont == null) { - FontUIResource f = fs.getTitleFont(); - titleFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); - } - return titleFont; - } - - @Override - public FontUIResource getWindowTitleFont() { - if (windowTitleFont == null) { - FontUIResource f = fs.getWindowTitleFont(); - windowTitleFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); - } - return windowTitleFont; - } - - @Override - public FontUIResource getSmallFont() { - if (smallFont == null) { - FontUIResource f = fs.getSmallFont(); - smallFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); - } - return smallFont; - } - - @Override - public FontUIResource getMessageFont() { - if (messageFont == null) { - FontUIResource f = fs.getMessageFont(); - messageFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); - } - return messageFont; - } - }; - - @Override - public FontSet getFontSet(String string, UIDefaults uid) { - return fontSet; - } - }); - } catch (UnsupportedLookAndFeelException ex) { - Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); - } - - UIManager.put(SubstanceLookAndFeel.TABBED_PANE_CONTENT_BORDER_KIND, SubstanceConstants.TabContentPaneBorderKind.SINGLE_FULL); - - JFrame.setDefaultLookAndFeelDecorated(true); - JDialog.setDefaultLookAndFeelDecorated(true); - } - - /** - * Loads image from resources - * - * @param name Name of the image - * @return loaded Image - */ - public static BufferedImage loadImage(String name) { - URL imageURL = View.class.getResource("/com/jpexs/decompiler/flash/gui/graphics/" + name + ".png"); - try { - return ImageIO.read(imageURL); - } catch (IOException ex) { - return null; - } - } - - /** - * Sets icon of specified frame to ASDec icon - * - * @param f Frame to set icon in - */ - public static void setWindowIcon(Window f) { - if (Configuration.useRibbonInterface.get()) { - List images = new ArrayList<>(); - MyResizableIcon[] icons = MyRibbonApplicationMenuButtonUI.getIcons(); - MyResizableIcon icon = icons[1]; - 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); - icon.paintIcon(f, bi.getGraphics(), 0, 0); - images.add(bi); - } - f.setIconImages(images); - } else { - List images = new ArrayList<>(); - images.add(loadImage("icon16")); - images.add(loadImage("icon32")); - images.add(loadImage("icon48")); - images.add(loadImage("icon256")); - f.setIconImages(images); - } - } - - /** - * Centers specified frame on the screen - * - * @param f Frame to center on the screen - */ - public static void centerScreen(Window f) { - centerScreen(f, 0); // todo, set screen to the currently active screen instead of the first screen in a multi screen setup, (maybe by using the screen where the main window is now classic or ribbon?) - } - - public static void centerScreen(Window f, int screen) { - - GraphicsDevice[] allDevices = getEnv().getScreenDevices(); - int topLeftX, topLeftY, screenX, screenY, windowPosX, windowPosY; - - if (screen < allDevices.length && screen > -1) { - topLeftX = allDevices[screen].getDefaultConfiguration().getBounds().x; - topLeftY = allDevices[screen].getDefaultConfiguration().getBounds().y; - - screenX = allDevices[screen].getDefaultConfiguration().getBounds().width; - screenY = allDevices[screen].getDefaultConfiguration().getBounds().height; - } else { - topLeftX = allDevices[0].getDefaultConfiguration().getBounds().x; - topLeftY = allDevices[0].getDefaultConfiguration().getBounds().y; - - screenX = allDevices[0].getDefaultConfiguration().getBounds().width; - screenY = allDevices[0].getDefaultConfiguration().getBounds().height; - } - - windowPosX = ((screenX - f.getWidth()) / 2) + topLeftX; - windowPosY = ((screenY - f.getHeight()) / 2) + topLeftY; - - f.setLocation(windowPosX, windowPosY); - } - - public static ImageIcon getIcon(String name, int size) { - ImageIcon icon = getIcon(getPrefferedIconName(name, size)); - if (icon.getIconWidth() == size && icon.getIconHeight() == size) { - return icon; - } - icon.getImage(); - BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); - bi.createGraphics().drawImage(icon.getImage(), 0, 0, size, size, null, null); - return new ImageIcon(bi); - } - - public static ImageIcon getIcon(String name) { - return new ImageIcon(View.class.getClassLoader().getResource("com/jpexs/decompiler/flash/gui/graphics/" + name + ".png")); - } - - private static final KeyStroke escapeStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); - - private static final String dispatchWindowClosingActionMapKey = "com.jpexs.dispatch:WINDOW_CLOSING"; - - public static void installEscapeCloseOperation(final JDialog dialog) { - Action dispatchClosing = new AbstractAction() { - @Override - public void actionPerformed(ActionEvent event) { - dialog.dispatchEvent(new WindowEvent( - dialog, WindowEvent.WINDOW_CLOSING)); - } - }; - JRootPane root = dialog.getRootPane(); - root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( - escapeStroke, dispatchWindowClosingActionMapKey); - root.getActionMap().put(dispatchWindowClosingActionMapKey, dispatchClosing); - } - - public static boolean iconExists(String resource) { - return View.class.getResource("/com/jpexs/decompiler/flash/gui/graphics/" + resource + ".png") != null; - } - - private static String getPrefferedIconName(String resource, int preferredSize) { - Matcher m = Pattern.compile("(.*[^0-9])([0-9]+)").matcher(resource); - if (m.matches()) { - int origSize = Integer.parseInt(m.group(2)); - String name = m.group(1); - if (origSize != preferredSize) { - if (iconExists(name + preferredSize)) { - return name + preferredSize; - } - } - } - return resource; - } - - public static ImageWrapperResizableIcon getResizableIcon(String resource, int preferredSize) { - return getResizableIcon(getPrefferedIconName(resource, preferredSize)); - } - - public static ImageWrapperResizableIcon getResizableIcon(String resource) { - return ImageWrapperResizableIcon.getIcon(View.class.getResource("/com/jpexs/decompiler/flash/gui/graphics/" + resource + ".png"), new Dimension(256, 256)); - } - - public static MyResizableIcon getMyResizableIcon(String resource) { - try { - return new MyResizableIcon(ImageIO.read(View.class.getResourceAsStream("/com/jpexs/decompiler/flash/gui/graphics/" + resource + ".png"))); - } catch (IOException ex) { - Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); - return null; - } - } - - public static void execInEventDispatch(Runnable r) { - if (SwingUtilities.isEventDispatchThread()) { - r.run(); - } else { - try { - SwingUtilities.invokeAndWait(r); - } catch (InterruptedException ex) { - } catch (InvocationTargetException ex) { - Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - - public static void execInEventDispatchLater(Runnable r) { - if (SwingUtilities.isEventDispatchThread()) { - r.run(); - } else { - SwingUtilities.invokeLater(r); - } - } - - public static int showOptionDialog(final Component parentComponent, final Object message, final String title, final int optionType, final int messageType, final Icon icon, final Object[] options, final Object initialValue) { - final int[] ret = new int[1]; - execInEventDispatch(() -> { - ret[0] = JOptionPane.showOptionDialog(parentComponent, message, title, optionType, messageType, icon, options, initialValue); - }); - return ret[0]; - } - - public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType) { - return showConfirmDialog(parentComponent, message, title, optionType, JOptionPane.PLAIN_MESSAGE); - } - - 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]; - execInEventDispatch(() -> { - ret[0] = JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageTyp); - }); - return ret[0]; - } - - public static int showConfirmDialog(Component parentComponent, String message, String title, int optionType, ConfigurationItem showAgainConfig, int defaultOption) { - return showConfirmDialog(parentComponent, message, title, optionType, JOptionPane.PLAIN_MESSAGE, showAgainConfig, defaultOption); - } - - public static int showConfirmDialog(final Component parentComponent, String message, final String title, final int optionType, final int messageType, ConfigurationItem showAgainConfig, int defaultOption) { - - JCheckBox donotShowAgainCheckBox = null; - JPanel warPanel = null; - if (showAgainConfig != null) { - if (!showAgainConfig.get()) { - return defaultOption; - } - - JLabel warLabel = new JLabel("" + message.replace("\r\n", "
") + ""); - warPanel = new JPanel(new BorderLayout()); - warPanel.add(warLabel, BorderLayout.CENTER); - donotShowAgainCheckBox = new JCheckBox(AppStrings.translate("message.confirm.donotshowagain")); - warPanel.add(donotShowAgainCheckBox, BorderLayout.SOUTH); - } - - final int ret[] = new int[1]; - final Object messageObj = warPanel == null ? message : warPanel; - execInEventDispatch(() -> { - ret[0] = JOptionPane.showConfirmDialog(parentComponent, messageObj, title, optionType, messageType); - }); - - if (donotShowAgainCheckBox != null) { - showAgainConfig.set(!donotShowAgainCheckBox.isSelected()); - } - - return ret[0]; - } - - public static void showMessageDialog(final Component parentComponent, final String message, final String title, final int messageType) { - showMessageDialog(parentComponent, message, title, messageType, null); - } - - public static void showMessageDialog(final Component parentComponent, final String message, final String title, final int messageType, ConfigurationItem showAgainConfig) { - - execInEventDispatch(() -> { - Object msg = message; - JCheckBox donotShowAgainCheckBox = null; - if (showAgainConfig != null) { - if (!showAgainConfig.get()) { - return; - } - - JLabel warLabel = new JLabel("" + message.replace("\r\n", "
") + ""); - final JPanel warPanel = new JPanel(new BorderLayout()); - warPanel.add(warLabel, BorderLayout.CENTER); - donotShowAgainCheckBox = new JCheckBox(AppStrings.translate("message.confirm.donotshowagain")); - warPanel.add(donotShowAgainCheckBox, BorderLayout.SOUTH); - msg = warPanel; - } - final Object fmsg = msg; - - JOptionPane.showMessageDialog(parentComponent, fmsg, title, messageType); - if (donotShowAgainCheckBox != null) { - showAgainConfig.set(!donotShowAgainCheckBox.isSelected()); - } - }); - } - - public static void showMessageDialog(final Component parentComponent, final Object message) { - execInEventDispatch(() -> { - JOptionPane.showMessageDialog(parentComponent, message); - }); - } - - public static String showInputDialog(final Object message, final Object initialSelection) { - final String[] ret = new String[1]; - execInEventDispatch(() -> { - ret[0] = JOptionPane.showInputDialog(message, initialSelection); - }); - return ret[0]; - } - - public static SubstanceColorScheme getColorScheme() { - return SubstanceColorSchemeUtilities.getActiveColorScheme(new JButton(), ComponentState.ENABLED); - } - - public static void refreshTree(JTree tree, TreeModel model) { - List> expandedNodes = getExpandedNodes(tree); - tree.setModel(model); - expandTreeNodes(tree, expandedNodes); - } - - public static List> getExpandedNodes(JTree tree) { - List> expandedNodes = new ArrayList<>(); - int rowCount = tree.getRowCount(); - for (int i = 0; i < rowCount; i++) { - try { - TreePath path = tree.getPathForRow(i); - if (tree.isExpanded(path)) { - List pathAsStringList = new ArrayList<>(); - for (Object pathCompnent : path.getPath()) { - pathAsStringList.add(pathCompnent.toString()); - } - expandedNodes.add(pathAsStringList); - } - } catch (IndexOutOfBoundsException | NullPointerException ex) { - // TreeNode was removed, ignore - } - } - return expandedNodes; - } - - public static void expandTreeNodes(JTree tree, List> pathsToExpand) { - for (List pathAsStringList : pathsToExpand) { - expandTreeNode(tree, pathAsStringList); - } - } - - private static TreePath expandTreeNode(JTree tree, List pathAsStringList) { - TreePath tp = getTreePathByPathStrings(tree, pathAsStringList); - tree.expandPath(tp); - return tp; - } - - public static TreePath getTreePathByPathStrings(JTree tree, List pathAsStringList) { - TreeModel model = tree.getModel(); - if (model == null) { - return null; - } - - Object node = model.getRoot(); - - if (pathAsStringList.isEmpty()) { - return null; - } - if (!pathAsStringList.get(0).equals(node.toString())) { - return null; - } - - List path = new ArrayList<>(); - path.add(node); - - for (int i = 1; i < pathAsStringList.size(); i++) { - String name = pathAsStringList.get(i); - int childCount = model.getChildCount(node); - for (int j = 0; j < childCount; j++) { - Object child = model.getChild(node, j); - if (child.toString().equals(name)) { - node = child; - path.add(node); - break; - } - } - } - - TreePath tp = new TreePath(path.toArray(new Object[path.size()])); - return tp; - } - - public static void expandTreeNodes(JTree tree, TreePath parent, boolean expand) { - expandTreeNodesRecursive(tree, parent, expand); - } - - private static void expandTreeNodesRecursive(JTree tree, TreePath parent, boolean expand) { - TreeModel model = tree.getModel(); - - Object node = parent.getLastPathComponent(); - int childCount = model.getChildCount(node); - for (int j = 0; j < childCount; j++) { - Object child = model.getChild(node, j); - TreePath path = parent.pathByAddingChild(child); - expandTreeNodesRecursive(tree, path, expand); - } - - if (expand) { - tree.expandPath(parent); - } else { - tree.collapsePath(parent); - } - } - - public static void addEditorAction(JEditorPane editor, AbstractAction a, String key, String name, String keyStroke) { - KeyStroke ks = KeyStroke.getKeyStroke(keyStroke); - a.putValue(Action.ACCELERATOR_KEY, ks); - a.putValue(Action.NAME, name); - - String actionName = key; - ActionMap amap = editor.getActionMap(); - InputMap imap = editor.getInputMap(JTextComponent.WHEN_FOCUSED); - imap.put(ks, actionName); - amap.put(actionName, a); - - JPopupMenu pmenu = editor.getComponentPopupMenu(); - JMenuItem findUsagesMenu = new JMenuItem(a); - pmenu.add(findUsagesMenu); - } - - public static boolean navigateUrl(String url) { - if (Desktop.isDesktopSupported()) { - Desktop desktop = Desktop.getDesktop(); - if (desktop.isSupported(Desktop.Action.BROWSE)) { - try { - URI uri = new URI(url); - desktop.browse(uri); - return true; - } catch (URISyntaxException | IOException ex) { - Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - - return false; - } - - public static JTable autoResizeColWidth(final JTable table, final TableModel model) { - View.execInEventDispatch(() -> { - table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); - table.setModel(model); - - int margin = 5; - - for (int i = 0; i < table.getColumnCount(); i++) { - int vColIndex = i; - DefaultTableColumnModel colModel = (DefaultTableColumnModel) table.getColumnModel(); - TableColumn col = colModel.getColumn(vColIndex); - int width; - - // Get width of column header - TableCellRenderer renderer = col.getHeaderRenderer(); - - if (renderer == null) { - renderer = table.getTableHeader().getDefaultRenderer(); - } - - Component comp = renderer.getTableCellRendererComponent(table, col.getHeaderValue(), false, false, 0, 0); - - width = comp.getPreferredSize().width; - - // Get maximum width of column data - for (int r = 0; r < table.getRowCount(); r++) { - renderer = table.getCellRenderer(r, vColIndex); - comp = renderer.getTableCellRendererComponent(table, table.getValueAt(r, vColIndex), false, false, - r, vColIndex); - width = Math.max(width, comp.getPreferredSize().width); - } - - // Add margin - width += 2 * margin; - - // Set the width - col.setPreferredWidth(width); - } - - ((DefaultTableCellRenderer) table.getTableHeader().getDefaultRenderer()).setHorizontalAlignment( - SwingConstants.LEFT); - - // table.setAutoCreateRowSorter(true); - table.getTableHeader().setReorderingAllowed(false); - }); - - return table; - } - - private static GraphicsEnvironment env; - - public static GraphicsEnvironment getEnv() { - if (env == null) { - env = GraphicsEnvironment.getLocalGraphicsEnvironment(); - } - return env; - } - - private static GraphicsConfiguration conf; - - public static GraphicsConfiguration getDefaultConfiguration() { - if (conf == null) { - conf = getEnv().getDefaultScreenDevice().getDefaultConfiguration(); - } - return conf; - } - - public static BufferedImage toCompatibleImage(BufferedImage image) { - if (image.getColorModel().equals(getDefaultConfiguration().getColorModel())) { - return image; - } - - return getDefaultConfiguration().createCompatibleImage(image.getWidth(), image.getHeight(), image.getTransparency()); - } - - public static VolatileImage createRenderImage(int width, int height, int transparency) { - VolatileImage image = getDefaultConfiguration().createCompatibleVolatileImage(width, height, transparency); - - int valid = image.validate(getDefaultConfiguration()); - - if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { - image = createRenderImage(width, height, transparency); - return image; - } - - return image; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.configuration.ConfigurationItem; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Desktop; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.SystemColor; +import java.awt.TexturePaint; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.awt.image.VolatileImage; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.imageio.ImageIO; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JRootPane; +import javax.swing.JTable; +import javax.swing.JTree; +import javax.swing.KeyStroke; +import javax.swing.LookAndFeel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.UIDefaults; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.plaf.FontUIResource; +import javax.swing.plaf.basic.BasicColorChooserUI; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableColumnModel; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableModel; +import javax.swing.text.JTextComponent; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; +import org.pushingpixels.flamingo.api.common.icon.ImageWrapperResizableIcon; +import org.pushingpixels.substance.api.ColorSchemeAssociationKind; +import org.pushingpixels.substance.api.ComponentState; +import org.pushingpixels.substance.api.DecorationAreaType; +import org.pushingpixels.substance.api.SubstanceColorScheme; +import org.pushingpixels.substance.api.SubstanceConstants; +import org.pushingpixels.substance.api.SubstanceLookAndFeel; +import org.pushingpixels.substance.api.SubstanceSkin; +import org.pushingpixels.substance.api.fonts.FontPolicy; +import org.pushingpixels.substance.api.fonts.FontSet; +import org.pushingpixels.substance.api.skin.SubstanceOfficeBlue2007LookAndFeel; +import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities; + +/** + * Contains methods for GUI + * + * @author JPEXS + */ +public class View { + + public static Color getDefaultBackgroundColor() { + if (Configuration.useRibbonInterface.get()) { + return SubstanceLookAndFeel.getCurrentSkin().getColorScheme(DecorationAreaType.GENERAL, ColorSchemeAssociationKind.FILL, ComponentState.ENABLED).getBackgroundFillColor(); + } else { + return SystemColor.control; + } + } + + private static Color swfBackgroundColor = null; + + public static void setSwfBackgroundColor(Color swfBackgroundColor) { + View.swfBackgroundColor = swfBackgroundColor; + } + + public static Color getSwfBackgroundColor() { + if (swfBackgroundColor == null) { + return getDefaultBackgroundColor(); + } + return swfBackgroundColor; + } + + private static final BufferedImage transparentTexture; + + public static final TexturePaint transparentPaint; + + private static final Color transparentColor1 = new Color(0x99, 0x99, 0x99); + + private static final Color transparentColor2 = new Color(0x66, 0x66, 0x66); + + static { + transparentTexture = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + Graphics g = transparentTexture.getGraphics(); + g.setColor(transparentColor1); + g.fillRect(0, 0, 16, 16); + g.setColor(transparentColor2); + g.fillRect(0, 0, 8, 8); + g.fillRect(8, 8, 8, 8); + transparentPaint = new TexturePaint(View.transparentTexture, new Rectangle(0, 0, transparentTexture.getWidth(), transparentTexture.getHeight())); + } + + /** + * Sets windows Look and Feel + */ + public static void setLookAndFeel() { + + //Save default font for Chinese characters + final Font defaultFont = (new JLabel()).getFont(); + try { + + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + + } catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) { + } + + try { + LookAndFeel oldLookAndFeel = UIManager.getLookAndFeel(); + if (!(oldLookAndFeel instanceof SubstanceOfficeBlue2007LookAndFeel)) { + UIManager.setLookAndFeel(new SubstanceOfficeBlue2007LookAndFeel()); + oldLookAndFeel.uninitialize(); + } + + SubstanceSkin currentSkin = SubstanceLookAndFeel.getCurrentSkin(); + if (currentSkin != null) { + String currentSkinName = currentSkin.getClass().getName(); + String newSkinName = Configuration.guiSkin.get(); + if (!currentSkinName.equals(newSkinName)) { + SubstanceLookAndFeel.setSkin(newSkinName); + } + } else { + Logger.getLogger(View.class.getName()).log(Level.SEVERE, "Current skin is null"); + SubstanceLookAndFeel.setSkin("com.jpexs.decompiler.flash.gui.OceanicSkin"); + } + + UIManager.put(SubstanceLookAndFeel.COLORIZATION_FACTOR, 0.999);//This works for not changing labels color and not changing Dialogs title + UIManager.put("Tree.expandedIcon", getIcon("expand16")); + UIManager.put("Tree.collapsedIcon", getIcon("collapse16")); + UIManager.put("ColorChooserUI", BasicColorChooserUI.class.getName()); + UIManager.put("ColorChooser.swatchesRecentSwatchSize", new Dimension(20, 20)); + UIManager.put("ColorChooser.swatchesSwatchSize", new Dimension(20, 20)); + UIManager.put("RibbonApplicationMenuPopupPanelUI", MyRibbonApplicationMenuPopupPanelUI.class.getName()); + UIManager.put("RibbonApplicationMenuButtonUI", MyRibbonApplicationMenuButtonUI.class.getName()); + UIManager.put("ProgressBarUI", MyProgressBarUI.class.getName()); + UIManager.put("TextField.background", Color.white); + UIManager.put("FormattedTextField.background", Color.white); + UIManager.put("CommandButtonUI", MyCommandButtonUI.class.getName()); + + FontPolicy pol = SubstanceLookAndFeel.getFontPolicy(); + final FontSet fs = pol.getFontSet("Substance", null); + + //Restore default font for chinese characters + SubstanceLookAndFeel.setFontPolicy(new FontPolicy() { + + private final FontSet fontSet = new FontSet() { + + private FontUIResource controlFont; + + private FontUIResource menuFont; + + private FontUIResource titleFont; + + private FontUIResource windowTitleFont; + + private FontUIResource smallFont; + + private FontUIResource messageFont; + + @Override + public FontUIResource getControlFont() { + if (controlFont == null) { + FontUIResource f = fs.getControlFont(); + controlFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); + } + return controlFont; + } + + @Override + public FontUIResource getMenuFont() { + if (menuFont == null) { + FontUIResource f = fs.getMenuFont(); + menuFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); + } + return menuFont; + } + + @Override + public FontUIResource getTitleFont() { + if (titleFont == null) { + FontUIResource f = fs.getTitleFont(); + titleFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); + } + return titleFont; + } + + @Override + public FontUIResource getWindowTitleFont() { + if (windowTitleFont == null) { + FontUIResource f = fs.getWindowTitleFont(); + windowTitleFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); + } + return windowTitleFont; + } + + @Override + public FontUIResource getSmallFont() { + if (smallFont == null) { + FontUIResource f = fs.getSmallFont(); + smallFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); + } + return smallFont; + } + + @Override + public FontUIResource getMessageFont() { + if (messageFont == null) { + FontUIResource f = fs.getMessageFont(); + messageFont = new FontUIResource(defaultFont.getName(), f.getStyle(), f.getSize()); + } + return messageFont; + } + }; + + @Override + public FontSet getFontSet(String string, UIDefaults uid) { + return fontSet; + } + }); + } catch (UnsupportedLookAndFeelException ex) { + Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); + } + + UIManager.put(SubstanceLookAndFeel.TABBED_PANE_CONTENT_BORDER_KIND, SubstanceConstants.TabContentPaneBorderKind.SINGLE_FULL); + + JFrame.setDefaultLookAndFeelDecorated(true); + JDialog.setDefaultLookAndFeelDecorated(true); + } + + /** + * Loads image from resources + * + * @param name Name of the image + * @return loaded Image + */ + public static BufferedImage loadImage(String name) { + URL imageURL = View.class.getResource("/com/jpexs/decompiler/flash/gui/graphics/" + name + ".png"); + try { + return ImageIO.read(imageURL); + } catch (IOException ex) { + return null; + } + } + + /** + * Sets icon of specified frame to ASDec icon + * + * @param f Frame to set icon in + */ + public static void setWindowIcon(Window f) { + if (Configuration.useRibbonInterface.get()) { + List images = new ArrayList<>(); + MyResizableIcon[] icons = MyRibbonApplicationMenuButtonUI.getIcons(); + MyResizableIcon icon = icons[1]; + 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); + icon.paintIcon(f, bi.getGraphics(), 0, 0); + images.add(bi); + } + f.setIconImages(images); + } else { + List images = new ArrayList<>(); + images.add(loadImage("icon16")); + images.add(loadImage("icon32")); + images.add(loadImage("icon48")); + images.add(loadImage("icon256")); + f.setIconImages(images); + } + } + + /** + * Centers specified frame on the screen + * + * @param f Frame to center on the screen + */ + public static void centerScreen(Window f) { + centerScreen(f, 0); // todo, set screen to the currently active screen instead of the first screen in a multi screen setup, (maybe by using the screen where the main window is now classic or ribbon?) + } + + public static void centerScreen(Window f, int screen) { + + GraphicsDevice[] allDevices = getEnv().getScreenDevices(); + int topLeftX, topLeftY, screenX, screenY, windowPosX, windowPosY; + + if (screen < allDevices.length && screen > -1) { + topLeftX = allDevices[screen].getDefaultConfiguration().getBounds().x; + topLeftY = allDevices[screen].getDefaultConfiguration().getBounds().y; + + screenX = allDevices[screen].getDefaultConfiguration().getBounds().width; + screenY = allDevices[screen].getDefaultConfiguration().getBounds().height; + } else { + topLeftX = allDevices[0].getDefaultConfiguration().getBounds().x; + topLeftY = allDevices[0].getDefaultConfiguration().getBounds().y; + + screenX = allDevices[0].getDefaultConfiguration().getBounds().width; + screenY = allDevices[0].getDefaultConfiguration().getBounds().height; + } + + windowPosX = ((screenX - f.getWidth()) / 2) + topLeftX; + windowPosY = ((screenY - f.getHeight()) / 2) + topLeftY; + + f.setLocation(windowPosX, windowPosY); + } + + public static ImageIcon getIcon(String name, int size) { + ImageIcon icon = getIcon(getPrefferedIconName(name, size)); + if (icon.getIconWidth() == size && icon.getIconHeight() == size) { + return icon; + } + icon.getImage(); + BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + bi.createGraphics().drawImage(icon.getImage(), 0, 0, size, size, null, null); + return new ImageIcon(bi); + } + + public static ImageIcon getIcon(String name) { + return new ImageIcon(View.class.getClassLoader().getResource("com/jpexs/decompiler/flash/gui/graphics/" + name + ".png")); + } + + private static final KeyStroke escapeStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + + private static final String dispatchWindowClosingActionMapKey = "com.jpexs.dispatch:WINDOW_CLOSING"; + + public static void installEscapeCloseOperation(final JDialog dialog) { + Action dispatchClosing = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent event) { + dialog.dispatchEvent(new WindowEvent( + dialog, WindowEvent.WINDOW_CLOSING)); + } + }; + JRootPane root = dialog.getRootPane(); + root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( + escapeStroke, dispatchWindowClosingActionMapKey); + root.getActionMap().put(dispatchWindowClosingActionMapKey, dispatchClosing); + } + + public static boolean iconExists(String resource) { + return View.class.getResource("/com/jpexs/decompiler/flash/gui/graphics/" + resource + ".png") != null; + } + + private static String getPrefferedIconName(String resource, int preferredSize) { + Matcher m = Pattern.compile("(.*[^0-9])([0-9]+)").matcher(resource); + if (m.matches()) { + int origSize = Integer.parseInt(m.group(2)); + String name = m.group(1); + if (origSize != preferredSize) { + if (iconExists(name + preferredSize)) { + return name + preferredSize; + } + } + } + return resource; + } + + public static ImageWrapperResizableIcon getResizableIcon(String resource, int preferredSize) { + return getResizableIcon(getPrefferedIconName(resource, preferredSize)); + } + + public static ImageWrapperResizableIcon getResizableIcon(String resource) { + return ImageWrapperResizableIcon.getIcon(View.class.getResource("/com/jpexs/decompiler/flash/gui/graphics/" + resource + ".png"), new Dimension(256, 256)); + } + + public static MyResizableIcon getMyResizableIcon(String resource) { + try { + return new MyResizableIcon(ImageIO.read(View.class.getResourceAsStream("/com/jpexs/decompiler/flash/gui/graphics/" + resource + ".png"))); + } catch (IOException ex) { + Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); + return null; + } + } + + public static void execInEventDispatch(Runnable r) { + if (SwingUtilities.isEventDispatchThread()) { + r.run(); + } else { + try { + SwingUtilities.invokeAndWait(r); + } catch (InterruptedException ex) { + } catch (InvocationTargetException ex) { + Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + public static void execInEventDispatchLater(Runnable r) { + if (SwingUtilities.isEventDispatchThread()) { + r.run(); + } else { + SwingUtilities.invokeLater(r); + } + } + + public static int showOptionDialog(final Component parentComponent, final Object message, final String title, final int optionType, final int messageType, final Icon icon, final Object[] options, final Object initialValue) { + final int[] ret = new int[1]; + execInEventDispatch(() -> { + ret[0] = JOptionPane.showOptionDialog(parentComponent, message, title, optionType, messageType, icon, options, initialValue); + }); + return ret[0]; + } + + public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType) { + return showConfirmDialog(parentComponent, message, title, optionType, JOptionPane.PLAIN_MESSAGE); + } + + 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]; + execInEventDispatch(() -> { + ret[0] = JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageTyp); + }); + return ret[0]; + } + + public static int showConfirmDialog(Component parentComponent, String message, String title, int optionType, ConfigurationItem showAgainConfig, int defaultOption) { + return showConfirmDialog(parentComponent, message, title, optionType, JOptionPane.PLAIN_MESSAGE, showAgainConfig, defaultOption); + } + + public static int showConfirmDialog(final Component parentComponent, String message, final String title, final int optionType, final int messageType, ConfigurationItem showAgainConfig, int defaultOption) { + + JCheckBox donotShowAgainCheckBox = null; + JPanel warPanel = null; + if (showAgainConfig != null) { + if (!showAgainConfig.get()) { + return defaultOption; + } + + JLabel warLabel = new JLabel("" + message.replace("\r\n", "
") + ""); + warPanel = new JPanel(new BorderLayout()); + warPanel.add(warLabel, BorderLayout.CENTER); + donotShowAgainCheckBox = new JCheckBox(AppStrings.translate("message.confirm.donotshowagain")); + warPanel.add(donotShowAgainCheckBox, BorderLayout.SOUTH); + } + + final int[] ret = new int[1]; + final Object messageObj = warPanel == null ? message : warPanel; + execInEventDispatch(() -> { + ret[0] = JOptionPane.showConfirmDialog(parentComponent, messageObj, title, optionType, messageType); + }); + + if (donotShowAgainCheckBox != null) { + showAgainConfig.set(!donotShowAgainCheckBox.isSelected()); + } + + return ret[0]; + } + + public static void showMessageDialog(final Component parentComponent, final String message, final String title, final int messageType) { + showMessageDialog(parentComponent, message, title, messageType, null); + } + + public static void showMessageDialog(final Component parentComponent, final String message, final String title, final int messageType, ConfigurationItem showAgainConfig) { + + execInEventDispatch(() -> { + Object msg = message; + JCheckBox donotShowAgainCheckBox = null; + if (showAgainConfig != null) { + if (!showAgainConfig.get()) { + return; + } + + JLabel warLabel = new JLabel("" + message.replace("\r\n", "
") + ""); + final JPanel warPanel = new JPanel(new BorderLayout()); + warPanel.add(warLabel, BorderLayout.CENTER); + donotShowAgainCheckBox = new JCheckBox(AppStrings.translate("message.confirm.donotshowagain")); + warPanel.add(donotShowAgainCheckBox, BorderLayout.SOUTH); + msg = warPanel; + } + final Object fmsg = msg; + + JOptionPane.showMessageDialog(parentComponent, fmsg, title, messageType); + if (donotShowAgainCheckBox != null) { + showAgainConfig.set(!donotShowAgainCheckBox.isSelected()); + } + }); + } + + public static void showMessageDialog(final Component parentComponent, final Object message) { + execInEventDispatch(() -> { + JOptionPane.showMessageDialog(parentComponent, message); + }); + } + + public static String showInputDialog(final Object message, final Object initialSelection) { + final String[] ret = new String[1]; + execInEventDispatch(() -> { + ret[0] = JOptionPane.showInputDialog(message, initialSelection); + }); + return ret[0]; + } + + public static SubstanceColorScheme getColorScheme() { + return SubstanceColorSchemeUtilities.getActiveColorScheme(new JButton(), ComponentState.ENABLED); + } + + public static void refreshTree(JTree tree, TreeModel model) { + List> expandedNodes = getExpandedNodes(tree); + tree.setModel(model); + expandTreeNodes(tree, expandedNodes); + } + + public static List> getExpandedNodes(JTree tree) { + List> expandedNodes = new ArrayList<>(); + int rowCount = tree.getRowCount(); + for (int i = 0; i < rowCount; i++) { + try { + TreePath path = tree.getPathForRow(i); + if (tree.isExpanded(path)) { + List pathAsStringList = new ArrayList<>(); + for (Object pathCompnent : path.getPath()) { + pathAsStringList.add(pathCompnent.toString()); + } + expandedNodes.add(pathAsStringList); + } + } catch (IndexOutOfBoundsException | NullPointerException ex) { + // TreeNode was removed, ignore + } + } + return expandedNodes; + } + + public static void expandTreeNodes(JTree tree, List> pathsToExpand) { + for (List pathAsStringList : pathsToExpand) { + expandTreeNode(tree, pathAsStringList); + } + } + + private static TreePath expandTreeNode(JTree tree, List pathAsStringList) { + TreePath tp = getTreePathByPathStrings(tree, pathAsStringList); + tree.expandPath(tp); + return tp; + } + + public static TreePath getTreePathByPathStrings(JTree tree, List pathAsStringList) { + TreeModel model = tree.getModel(); + if (model == null) { + return null; + } + + Object node = model.getRoot(); + + if (pathAsStringList.isEmpty()) { + return null; + } + if (!pathAsStringList.get(0).equals(node.toString())) { + return null; + } + + List path = new ArrayList<>(); + path.add(node); + + for (int i = 1; i < pathAsStringList.size(); i++) { + String name = pathAsStringList.get(i); + int childCount = model.getChildCount(node); + for (int j = 0; j < childCount; j++) { + Object child = model.getChild(node, j); + if (child.toString().equals(name)) { + node = child; + path.add(node); + break; + } + } + } + + TreePath tp = new TreePath(path.toArray(new Object[path.size()])); + return tp; + } + + public static void expandTreeNodes(JTree tree, TreePath parent, boolean expand) { + expandTreeNodesRecursive(tree, parent, expand); + } + + private static void expandTreeNodesRecursive(JTree tree, TreePath parent, boolean expand) { + TreeModel model = tree.getModel(); + + Object node = parent.getLastPathComponent(); + int childCount = model.getChildCount(node); + for (int j = 0; j < childCount; j++) { + Object child = model.getChild(node, j); + TreePath path = parent.pathByAddingChild(child); + expandTreeNodesRecursive(tree, path, expand); + } + + if (expand) { + tree.expandPath(parent); + } else { + tree.collapsePath(parent); + } + } + + public static void addEditorAction(JEditorPane editor, AbstractAction a, String key, String name, String keyStroke) { + KeyStroke ks = KeyStroke.getKeyStroke(keyStroke); + a.putValue(Action.ACCELERATOR_KEY, ks); + a.putValue(Action.NAME, name); + + String actionName = key; + ActionMap amap = editor.getActionMap(); + InputMap imap = editor.getInputMap(JTextComponent.WHEN_FOCUSED); + imap.put(ks, actionName); + amap.put(actionName, a); + + JPopupMenu pmenu = editor.getComponentPopupMenu(); + JMenuItem findUsagesMenu = new JMenuItem(a); + pmenu.add(findUsagesMenu); + } + + public static boolean navigateUrl(String url) { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + if (desktop.isSupported(Desktop.Action.BROWSE)) { + try { + URI uri = new URI(url); + desktop.browse(uri); + return true; + } catch (URISyntaxException | IOException ex) { + Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + return false; + } + + public static JTable autoResizeColWidth(final JTable table, final TableModel model) { + View.execInEventDispatch(() -> { + table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + table.setModel(model); + + int margin = 5; + + for (int i = 0; i < table.getColumnCount(); i++) { + int vColIndex = i; + DefaultTableColumnModel colModel = (DefaultTableColumnModel) table.getColumnModel(); + TableColumn col = colModel.getColumn(vColIndex); + int width; + + // Get width of column header + TableCellRenderer renderer = col.getHeaderRenderer(); + + if (renderer == null) { + renderer = table.getTableHeader().getDefaultRenderer(); + } + + Component comp = renderer.getTableCellRendererComponent(table, col.getHeaderValue(), false, false, 0, 0); + + width = comp.getPreferredSize().width; + + // Get maximum width of column data + for (int r = 0; r < table.getRowCount(); r++) { + renderer = table.getCellRenderer(r, vColIndex); + comp = renderer.getTableCellRendererComponent(table, table.getValueAt(r, vColIndex), false, false, + r, vColIndex); + width = Math.max(width, comp.getPreferredSize().width); + } + + // Add margin + width += 2 * margin; + + // Set the width + col.setPreferredWidth(width); + } + + ((DefaultTableCellRenderer) table.getTableHeader().getDefaultRenderer()).setHorizontalAlignment( + SwingConstants.LEFT); + + // table.setAutoCreateRowSorter(true); + table.getTableHeader().setReorderingAllowed(false); + }); + + return table; + } + + private static GraphicsEnvironment env; + + public static GraphicsEnvironment getEnv() { + if (env == null) { + env = GraphicsEnvironment.getLocalGraphicsEnvironment(); + } + return env; + } + + private static GraphicsConfiguration conf; + + public static GraphicsConfiguration getDefaultConfiguration() { + if (conf == null) { + conf = getEnv().getDefaultScreenDevice().getDefaultConfiguration(); + } + return conf; + } + + public static BufferedImage toCompatibleImage(BufferedImage image) { + if (image.getColorModel().equals(getDefaultConfiguration().getColorModel())) { + return image; + } + + return getDefaultConfiguration().createCompatibleImage(image.getWidth(), image.getHeight(), image.getTransparency()); + } + + public static VolatileImage createRenderImage(int width, int height, int transparency) { + VolatileImage image = getDefaultConfiguration().createCompatibleVolatileImage(width, height, transparency); + + int valid = image.validate(getDefaultConfiguration()); + + if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { + image = createRenderImage(width, height, transparency); + return image; + } + + return image; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java index b4014e147..9391bbcea 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java @@ -1,1325 +1,1325 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.abc; - -import com.jpexs.debugger.flash.Variable; -import com.jpexs.debugger.flash.VariableFlags; -import com.jpexs.debugger.flash.VariableType; -import com.jpexs.debugger.flash.messages.in.InGetVariable; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.abc.ABC; -import com.jpexs.decompiler.flash.abc.ClassPath; -import com.jpexs.decompiler.flash.abc.ScriptPack; -import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; -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.parser.AVM2ParseException; -import com.jpexs.decompiler.flash.abc.avm2.parser.script.Reference; -import com.jpexs.decompiler.flash.abc.types.ABCException; -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.ValueKind; -import com.jpexs.decompiler.flash.abc.types.traits.Trait; -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.abc.usages.MultinameUsage; -import com.jpexs.decompiler.flash.abc.usages.TraitMultinameUsage; -import com.jpexs.decompiler.flash.action.parser.ActionParseException; -import com.jpexs.decompiler.flash.action.parser.script.ActionScriptLexer; -import com.jpexs.decompiler.flash.action.parser.script.ParsedSymbol; -import com.jpexs.decompiler.flash.action.parser.script.SymbolType; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.ecma.EcmaScript; -import com.jpexs.decompiler.flash.gui.AppDialog; -import com.jpexs.decompiler.flash.gui.AppStrings; -import com.jpexs.decompiler.flash.gui.DebugPanel; -import com.jpexs.decompiler.flash.gui.DebuggerHandler; -import com.jpexs.decompiler.flash.gui.HeaderLabel; -import com.jpexs.decompiler.flash.gui.Main; -import com.jpexs.decompiler.flash.gui.MainPanel; -import com.jpexs.decompiler.flash.gui.SearchListener; -import com.jpexs.decompiler.flash.gui.SearchPanel; -import com.jpexs.decompiler.flash.gui.TagEditorPanel; -import com.jpexs.decompiler.flash.gui.View; -import com.jpexs.decompiler.flash.gui.abc.tablemodels.DecimalTableModel; -import com.jpexs.decompiler.flash.gui.abc.tablemodels.DoubleTableModel; -import com.jpexs.decompiler.flash.gui.abc.tablemodels.IntTableModel; -import com.jpexs.decompiler.flash.gui.abc.tablemodels.MultinameTableModel; -import com.jpexs.decompiler.flash.gui.abc.tablemodels.NamespaceSetTableModel; -import com.jpexs.decompiler.flash.gui.abc.tablemodels.NamespaceTableModel; -import com.jpexs.decompiler.flash.gui.abc.tablemodels.StringTableModel; -import com.jpexs.decompiler.flash.gui.abc.tablemodels.UIntTableModel; -import com.jpexs.decompiler.flash.gui.controls.JPersistentSplitPane; -import com.jpexs.decompiler.flash.gui.editor.LinkHandler; -import com.jpexs.decompiler.flash.gui.tagtree.TagTreeModel; -import com.jpexs.decompiler.flash.tags.ABCContainerTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.treeitems.TreeItem; -import com.jpexs.decompiler.graph.CompilationException; -import com.jpexs.helpers.CancellableWorker; -import com.jpexs.helpers.Helper; -import de.hameister.treetable.MyTreeTable; -import de.hameister.treetable.MyTreeTableModel; -import java.awt.BorderLayout; -import java.awt.Cursor; -import java.awt.FlowLayout; -import java.awt.Font; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; -import java.io.File; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Pattern; -import javax.swing.AbstractAction; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JTabbedPane; -import javax.swing.JTable; -import javax.swing.JToggleButton; -import javax.swing.SwingConstants; -import javax.swing.border.BevelBorder; -import javax.swing.event.EventListenerList; -import javax.swing.event.TableModelListener; -import javax.swing.event.TreeModelEvent; -import javax.swing.event.TreeModelListener; -import javax.swing.table.DefaultTableModel; -import javax.swing.text.Highlighter; -import javax.swing.tree.TreePath; -import jsyntaxpane.SyntaxDocument; -import jsyntaxpane.Token; -import jsyntaxpane.TokenType; - -/** - * - * @author JPEXS - */ -public class ABCPanel extends JPanel implements ItemListener, SearchListener, TagEditorPanel { - - private final MainPanel mainPanel; - - public final TraitsList navigator; - - public ABC abc; - - public final DecompiledEditorPane decompiledTextArea; - - public final JScrollPane decompiledScrollPane; - - private final JPersistentSplitPane splitPane; - - private final JTable constantTable; - - public JComboBox constantTypeList; - - public JLabel asmLabel = new HeaderLabel(AppStrings.translate("panel.disassembled")); - - public JLabel decLabel = new HeaderLabel(AppStrings.translate("panel.decompiled")); - - public final DetailPanel detailPanel; - - private final JPanel navPanel; - - public final JTabbedPane tabbedPane; - - public final SearchPanel searchPanel; - - private NewTraitDialog newTraitDialog; - - public final JLabel scriptNameLabel; - - private final DebugPanel debugPanel; - - private final JLabel experimentalLabel = new JLabel(AppStrings.translate("action.edit.experimental")); - - private final JButton editDecompiledButton = new JButton(AppStrings.translate("button.edit"), View.getIcon("edit16")); - - private final JButton saveDecompiledButton = new JButton(AppStrings.translate("button.save"), View.getIcon("save16")); - - private final JButton cancelDecompiledButton = new JButton(AppStrings.translate("button.cancel"), View.getIcon("cancel16")); - - private String lastDecompiled = null; - - public MainPanel getMainPanel() { - return mainPanel; - } - - public List search(final SWF swf, final String txt, boolean ignoreCase, boolean regexp, CancellableWorker worker) { - List ignoredClasses = new ArrayList<>(); - List ignoredNss = new ArrayList<>(); - - if (Configuration._ignoreAdditionalFlexClasses.get()) { - abc.getSwf().getFlexMainClass(ignoredClasses, ignoredNss); - } - if (txt != null && !txt.isEmpty()) { - searchPanel.setOptions(ignoreCase, regexp); - TagTreeModel ttm = (TagTreeModel) mainPanel.tagTree.getModel(); - TreeItem scriptsNode = ttm.getScriptsNode(swf); - final List found = new ArrayList<>(); - if (scriptsNode instanceof ClassesListTreeModel) { - ClassesListTreeModel clModel = (ClassesListTreeModel) scriptsNode; - List allpacks = clModel.getList(); - final Pattern pat = regexp - ? Pattern.compile(txt, ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0) - : Pattern.compile(Pattern.quote(txt), ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0); - int pos = 0; - loop: - for (final ScriptPack pack : allpacks) { - pos++; - if (!pack.isSimple && Configuration.ignoreCLikePackages.get()) { - continue; - } - if (Configuration._ignoreAdditionalFlexClasses.get()) { - String fullName = pack.getClassPath().packageStr.add(pack.getClassPath().className).toRawString(); - if (ignoredClasses.contains(fullName)) { - continue; - } - for (String ns : ignoredNss) { - if (fullName.startsWith(ns + ".")) { - continue loop; - } - } - } - - String workText = AppStrings.translate("work.searching"); - String decAdd = ""; - if (!SWF.isCached(pack)) { - decAdd = ", " + AppStrings.translate("work.decompiling"); - } - - Main.startWork(workText + " \"" + txt + "\"" + decAdd + " - (" + pos + "/" + allpacks.size() + ") " + pack.getClassPath().toString() + "... ", worker); - try { - if (pat.matcher(SWF.getCached(pack).text).find()) { - ABCPanelSearchResult searchResult = new ABCPanelSearchResult(pack); - found.add(searchResult); - } - } catch (InterruptedException ex) { - break; - } - } - } - - return found; - } - - return null; - } - - public void setAbc(ABC abc) { - if (abc == this.abc) { - return; - } - this.abc = abc; - setDecompiledEditMode(false); - navigator.setAbc(abc); - updateConstList(); - } - - public void updateConstList() { - switch (constantTypeList.getSelectedIndex()) { - case 0: - View.autoResizeColWidth(constantTable, new UIntTableModel(abc)); - break; - case 1: - View.autoResizeColWidth(constantTable, new IntTableModel(abc)); - break; - case 2: - View.autoResizeColWidth(constantTable, new DoubleTableModel(abc)); - break; - case 3: - View.autoResizeColWidth(constantTable, new DecimalTableModel(abc)); - break; - case 4: - View.autoResizeColWidth(constantTable, new StringTableModel(abc)); - break; - case 5: - View.autoResizeColWidth(constantTable, new NamespaceTableModel(abc)); - break; - case 6: - View.autoResizeColWidth(constantTable, new NamespaceSetTableModel(abc)); - break; - case 7: - View.autoResizeColWidth(constantTable, new MultinameTableModel(abc)); - break; - } - //DefaultTableColumnModel colModel = (DefaultTableColumnModel) constantTable.getColumnModel(); - //colModel.getColumn(0).setMaxWidth(50); - } - - public SWF getSwf() { - return abc == null ? null : abc.getSwf(); - } - - public List getAbcList() { - SWF swf = getSwf(); - return swf == null ? null : swf.getAbcList(); - } - - public void clearSwf() { - this.abc = null; - constantTable.setModel(new DefaultTableModel()); - navigator.clearAbc(); - decompiledTextArea.clearScript(); - } - - public static class VariableNode { - - public List path = new ArrayList<>(); - - public Long parentId; - - public int level; - - public Variable thisVar; - - public Variable thisTrait; - - public long thisTraitId; - - private List childs; - - private List childTraits; - - @Override - public int hashCode() { - int hash = 3; - hash = 53 * hash + Objects.hashCode(this.parentId); - hash = 53 * hash + (this.thisVar == null ? 0 : Objects.hashCode(this.thisVar.name)); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final VariableNode other = (VariableNode) obj; - if (!Objects.equals(this.parentId, other.parentId)) { - return false; - } - if (this.thisVar == null && other.thisVar == null) { - return true; - } - if (this.thisVar == null) { - return false; - } - if (other.thisVar == null) { - return false; - } - return Objects.equals(this.thisVar.name, other.thisVar.name); - } - - public boolean loaded = false; - - private static boolean isTraits(Variable v) { - return (v.vType == VariableType.UNKNOWN && "traits".equals(v.typeName)); - } - - @Override - public String toString() { - if (level == 0) { - return "root"; //TODO: localize? - } - return thisVar.name; - } - - private void refresh() { - if (path.size() > 1) { - path.get(path.size() - 2).reloadChildren(); - } else { - //Main.getDebugHandler().refreshFrame(); - //InFrame fr = Main.getDebugHandler().getFrame(); - } - } - - private void reloadChildren() { - InGetVariable igv = Main.getDebugHandler().getVariable(parentId, thisVar.name, true); - childs = new ArrayList<>(); - childTraits = new ArrayList<>(); - if (thisVar.vType != VariableType.FUNCTION || ((thisVar.flags & VariableFlags.HAS_GETTER) > 0)) { - thisVar = igv.parent; - } - Variable curTrait = null; - - for (int i = 0; i < igv.childs.size(); i++) { - if (!isTraits(igv.childs.get(i))) { - childs.add(igv.childs.get(i)); - childTraits.add(curTrait); - } else { - curTrait = igv.childs.get(i); - } - } - } - - private void ensureLoaded() { - if (!loaded) { - reloadChildren(); - loaded = true; - } - } - - public VariableNode getChildAt(int index) { - ensureLoaded(); - Long parId = 0L; - if (thisVar != null && (thisVar.vType == VariableType.OBJECT || thisVar.vType == VariableType.MOVIECLIP)) { - parId = (Long) thisVar.value; - } - VariableNode vn = new VariableNode(level + 1, childs.get(index), parId, childTraits.get(index)); - vn.path.addAll(path); - vn.path.add(vn); - return vn; - } - - public int getChildCount() { - ensureLoaded(); - return childs.size(); - } - - public VariableNode(int level, Variable thisVar, Long parentId, Variable thisTrait) { - this.parentId = parentId; - this.thisVar = thisVar; - this.level = level; - this.thisTrait = thisTrait; - } - - public VariableNode(int level, Variable thisVar, Long parentId, Variable thisTrait, Long thisTraitId, List vars, List varTraits) { - this.parentId = parentId; - - this.thisVar = thisVar; - - this.level = level; - this.childs = vars; - - this.thisTrait = thisTrait; - - this.childTraits = varTraits; - this.path.add(this); - - loaded = true; - } - } - - public static class VariablesTableModel implements MyTreeTableModel { - - List tableListeners = new ArrayList<>(); - - VariableNode root; - - private final Map> nodeCache = new HashMap<>(); - - protected EventListenerList listenerList = new EventListenerList(); - - private static final int CHANGED = 0; - - private static final int INSERTED = 1; - - private static final int REMOVED = 2; - - private static final int STRUCTURE_CHANGED = 3; - - private final MyTreeTable ttable; - - public VariablesTableModel(MyTreeTable ttable, List vars, List parentIds) { - this.ttable = ttable; - List varTraits = new ArrayList<>(); - for (int i = 0; i < vars.size(); i++) { - varTraits.add(null); - } - root = new VariableNode(0, null, 0L, null, 0L, vars, varTraits); - } - - @Override - public int getColumnCount() { - return 3; - } - - @Override - public String getColumnName(int columnIndex) { - switch (columnIndex) { - case 0: - return AppStrings.translate("variables.column.name"); - case 1: - return AppStrings.translate("variables.column.type"); - case 2: - return AppStrings.translate("variables.column.value"); - default: - return null; - } - } - - @Override - public Class getColumnClass(int columnIndex) { - if (columnIndex == 0) { - return MyTreeTableModel.class; - } - return String.class; - } - - @Override - public Object getValueAt(Object node, int columnIndex) { - if (node == root) { - if (columnIndex == 0) { - return "root"; - } - return ""; - } - Variable v = ((VariableNode) node).thisVar; - - switch (columnIndex) { - case 0: - return v.name; - case 1: - String typeStr = v.getTypeAsStr(); - if ("Object".equals(typeStr)) { - typeStr = v.className; - } - if ("Object".equals(typeStr)) { - typeStr = v.typeName; - } - return typeStr; - case 2: - switch (v.vType) { - case VariableType.OBJECT: - case VariableType.MOVIECLIP: - case VariableType.FUNCTION: - return v.getTypeAsStr() + "(" + v.value + ")"; - case VariableType.STRING: - return "\"" + Helper.escapeActionScriptString("" + v.value) + "\""; - default: - return EcmaScript.toString(v.value); - } - - } - return null; - } - - @Override - public boolean isCellEditable(Object node, int column) { - return column == 0 || (column == 2 && node != root && ((VariableNode) node).thisVar.isPrimitive); - } - - @Override - public void setValueAt(Object aValue, Object node, int column) { - ActionScriptLexer lexer = new ActionScriptLexer(new StringReader("" + aValue)); - ParsedSymbol symb; - try { - symb = lexer.lex(); - ParsedSymbol f = lexer.yylex(); - if (f.type != SymbolType.EOF) { - return; - } - } catch (IOException | ActionParseException ex) { - return; - } - int valType; - switch (symb.type) { - case DOUBLE: - valType = VariableType.NUMBER; - break; - case INTEGER: - valType = VariableType.NUMBER; - break; - case NULL: - valType = VariableType.NULL; - break; - case STRING: - valType = VariableType.STRING; - break; - case UNDEFINED: - valType = VariableType.UNDEFINED; - break; - default: - return; - } - Main.getDebugHandler().setVariable(((VariableNode) node).parentId, ((VariableNode) node).thisVar.name, valType, symb.value); - //((VariableNode) node).refresh(); - Object path[] = new Object[((VariableNode) node).path.size()]; - for (int i = 0; i < path.length; i++) { - path[i] = ((VariableNode) node).path.get(i); - } - valueForPathChanged(new TreePath(path), aValue); - //fireTreeNodesChanged(this, path, new int[0]/*removed*/, new Object[]{node}); - } - - @Override - public Object getRoot() { - return root; - } - - @Override - public Object getChild(Object parent, int index) { - return ((VariableNode) parent).getChildAt(index); - } - - @Override - public int getChildCount(Object parent) { - int cnt = ((VariableNode) parent).getChildCount(); - return cnt; - } - - @Override - public boolean isLeaf(Object node) { - return getChildCount(node) == 0; - } - - @Override - public void valueForPathChanged(TreePath path, Object newValue) { - fireTreeNodesChanged(ttable, path.getParentPath().getPath(), new int[0], new Object[]{path.getLastPathComponent()}); - } - - @Override - public int getIndexOfChild(Object parent, Object child) { - int cnt = getChildCount(parent); - for (int i = 0; i < cnt; i++) { - if (getChild(parent, i) == child) { - return i; - } - } - return -1; - } - - @Override - public void addTreeModelListener(TreeModelListener l) { - listenerList.add(TreeModelListener.class, l); - } - - @Override - public void removeTreeModelListener(TreeModelListener l) { - listenerList.remove(TreeModelListener.class, l); - } - - protected void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) { - fireTreeNode(CHANGED, source, path, childIndices, children); - } - - protected void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { - fireTreeNode(INSERTED, source, path, childIndices, children); - } - - protected void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { - fireTreeNode(REMOVED, source, path, childIndices, children); - } - - protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { - fireTreeNode(STRUCTURE_CHANGED, source, path, childIndices, children); - } - - private void fireTreeNode(int changeType, Object source, Object[] path, int[] childIndices, Object[] children) { - Object[] listeners = listenerList.getListenerList(); - TreeModelEvent e = new TreeModelEvent(source, path, childIndices, children); - for (int i = listeners.length - 2; i >= 0; i -= 2) { - if (listeners[i] == TreeModelListener.class) { - - switch (changeType) { - case CHANGED: - ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e); - break; - case INSERTED: - ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e); - break; - case REMOVED: - ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e); - break; - case STRUCTURE_CHANGED: - ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e); - break; - default: - break; - } - - } - } - } - } - - public ABCPanel(MainPanel mainPanel) { - - this.mainPanel = mainPanel; - setLayout(new BorderLayout()); - - decompiledTextArea = new DecompiledEditorPane(this); - decompiledTextArea.addTextChangedListener(this::decompiledTextAreaTextChanged); - - decompiledTextArea.setLinkHandler(new LinkHandler() { - - @Override - public boolean isLink(Token token) { - return hasDeclaration(token.start); - } - - @Override - public void handleLink(Token token) { - gotoDeclaration(token.start); - } - - @Override - public Highlighter.HighlightPainter linkPainter() { - return decompiledTextArea.linkPainter(); - } - }); - - searchPanel = new SearchPanel<>(new FlowLayout(), this); - - decompiledScrollPane = new JScrollPane(decompiledTextArea); - - JPanel iconDecPanel = new JPanel(); - iconDecPanel.setLayout(new BoxLayout(iconDecPanel, BoxLayout.Y_AXIS)); - JPanel iconsPanel = new JPanel(); - iconsPanel.setLayout(new BoxLayout(iconsPanel, BoxLayout.X_AXIS)); - - JButton newTraitButton = new JButton(View.getIcon("traitadd16")); - newTraitButton.setMargin(new Insets(5, 5, 5, 5)); - newTraitButton.addActionListener(this::addTraitButtonActionPerformed); - newTraitButton.setToolTipText(AppStrings.translate("button.addtrait")); - iconsPanel.add(newTraitButton); - - scriptNameLabel = new JLabel("-"); - scriptNameLabel.setAlignmentX(0); - iconsPanel.setAlignmentX(0); - decompiledScrollPane.setAlignmentX(0); - iconDecPanel.add(scriptNameLabel); - iconDecPanel.add(iconsPanel); - iconDecPanel.add(decompiledScrollPane); - - final JPanel decButtonsPan = new JPanel(new FlowLayout()); - decButtonsPan.setBorder(new BevelBorder(BevelBorder.RAISED)); - decButtonsPan.add(editDecompiledButton); - decButtonsPan.add(experimentalLabel); - decButtonsPan.add(saveDecompiledButton); - decButtonsPan.add(cancelDecompiledButton); - - editDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); - saveDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); - cancelDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); - - saveDecompiledButton.addActionListener(this::saveDecompiledButtonActionPerformed); - editDecompiledButton.addActionListener(this::editDecompiledButtonActionPerformed); - cancelDecompiledButton.addActionListener(this::cancelDecompiledButtonActionPerformed); - - saveDecompiledButton.setVisible(false); - cancelDecompiledButton.setVisible(false); - decButtonsPan.setAlignmentX(0); - - JPanel decPanel = new JPanel(new BorderLayout()); - decPanel.add(searchPanel, BorderLayout.NORTH); - decPanel.add(iconDecPanel, BorderLayout.CENTER); - decPanel.add(decButtonsPan, BorderLayout.SOUTH); - detailPanel = new DetailPanel(this); - JPanel panB = new JPanel(); - panB.setLayout(new BorderLayout()); - panB.add(decLabel, BorderLayout.NORTH); - - Main.getDebugHandler().addConnectionListener(new DebuggerHandler.ConnectionListener() { - - @Override - public void connected() { - decButtonsPan.setVisible(false); - } - - @Override - public void disconnected() { - decButtonsPan.setVisible(true); - } - }); - - debugPanel = new DebugPanel(); - - JPersistentSplitPane sp2; - - panB.add(sp2 = new JPersistentSplitPane(JSplitPane.VERTICAL_SPLIT, decPanel, debugPanel, Configuration.guiAvm2VarsSplitPaneDividerLocationPercent), BorderLayout.CENTER); - sp2.setContinuousLayout(true); - - debugPanel.setVisible(false); - - decLabel.setHorizontalAlignment(SwingConstants.CENTER); - //decLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); - splitPane = new JPersistentSplitPane(JSplitPane.HORIZONTAL_SPLIT, panB, detailPanel, Configuration.guiAvm2SplitPaneDividerLocationPercent); - splitPane.setContinuousLayout(true); - - decompiledTextArea.changeContentType("text/actionscript"); - decompiledTextArea.setFont(new Font("Monospaced", Font.PLAIN, decompiledTextArea.getFont().getSize())); - - View.addEditorAction(decompiledTextArea, new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - int multinameIndex = decompiledTextArea.getMultinameUnderCaret(); - if (multinameIndex > -1) { - UsageFrame usageFrame = new UsageFrame(abc, multinameIndex, ABCPanel.this, false); - usageFrame.setVisible(true); - } - } - }, "find-usages", AppStrings.translate("abc.action.find-usages"), "control U"); - - View.addEditorAction(decompiledTextArea, new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - gotoDeclaration(decompiledTextArea.getCaretPosition()); - } - }, "find-declaration", AppStrings.translate("abc.action.find-declaration"), "control B"); - - CtrlClickHandler cch = new CtrlClickHandler(); - decompiledTextArea.addKeyListener(cch); - decompiledTextArea.addMouseListener(cch); - decompiledTextArea.addMouseMotionListener(cch); - - navigator = new TraitsList(this); - - navPanel = new JPanel(new BorderLayout()); - JPanel navIconsPanel = new JPanel(); - navIconsPanel.setLayout(new BoxLayout(navIconsPanel, BoxLayout.X_AXIS)); - final JToggleButton sortButton = new JToggleButton(View.getIcon("sort16")); - sortButton.setMargin(new Insets(3, 3, 3, 3)); - navIconsPanel.add(sortButton); - navPanel.add(navIconsPanel, BorderLayout.SOUTH); - navPanel.add(new JScrollPane(navigator), BorderLayout.CENTER); - sortButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - navigator.setSorted(sortButton.isSelected()); - navigator.updateUI(); - } - }); - - tabbedPane = new JTabbedPane(); - tabbedPane.addTab(AppStrings.translate("traits"), navPanel); - add(splitPane, BorderLayout.CENTER); - - JPanel panConstants = new JPanel(); - panConstants.setLayout(new BorderLayout()); - - constantTypeList = new JComboBox<>(new String[]{"UINT", "INT", "DOUBLE", "DECIMAL", "STRING", "NAMESPACE", "NAMESPACESET", "MULTINAME"}); - constantTable = new JTable(); - if (abc != null) { - View.autoResizeColWidth(constantTable, new UIntTableModel(abc)); - } - constantTable.setAutoCreateRowSorter(true); - - final ABCPanel t = this; - constantTable.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2) { - if (constantTypeList.getSelectedIndex() == 7) { //MULTINAME - int rowIndex = constantTable.getSelectedRow(); - if (rowIndex == -1) { - return; - } - int multinameIndex = constantTable.convertRowIndexToModel(rowIndex); - if (multinameIndex > 0) { - UsageFrame usageFrame = new UsageFrame(abc, multinameIndex, t, false); - usageFrame.setVisible(true); - } - } - } - } - }); - constantTypeList.addItemListener(this); - panConstants.add(constantTypeList, BorderLayout.NORTH); - panConstants.add(new JScrollPane(constantTable), BorderLayout.CENTER); - tabbedPane.addTab(AppStrings.translate("constants"), panConstants); - } - - private void decompiledTextAreaTextChanged() { - setModified(true); - } - - private boolean isModified() { - return saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled(); - } - - private void setModified(boolean value) { - saveDecompiledButton.setEnabled(value); - cancelDecompiledButton.setEnabled(value); - } - - private boolean hasDeclaration(int pos) { - if (decompiledTextArea == null) { - return false; //? - } - SyntaxDocument sd = (SyntaxDocument) decompiledTextArea.getDocument(); - Token t = sd.getTokenAt(pos + 1); - if (t == null || (t.type != TokenType.IDENTIFIER && t.type != TokenType.KEYWORD && t.type != TokenType.REGEX)) { - return false; - } - Reference abcIndex = new Reference<>(0); - Reference classIndex = new Reference<>(0); - Reference traitIndex = new Reference<>(0); - Reference multinameIndexRef = new Reference<>(0); - Reference classTrait = new Reference<>(false); - - if (decompiledTextArea.getPropertyTypeAtPos(pos, abcIndex, classIndex, traitIndex, classTrait, multinameIndexRef)) { - return true; - } - int multinameIndex = decompiledTextArea.getMultinameAtPos(pos); - if (multinameIndex > -1) { - if (multinameIndex == 0) { - return false; - } - List usages = abc.findMultinameDefinition(multinameIndex); - - Multiname m = abc.constants.getMultiname(multinameIndex); - - //search other ABC tags if this is not private multiname - if (m.getSingleNamespaceIndex(abc.constants) > 0 && abc.constants.getNamespace(m.getSingleNamespaceIndex(abc.constants)).kind != Namespace.KIND_PRIVATE) { - for (ABCContainerTag at : getAbcList()) { - ABC a = at.getABC(); - if (a == abc) { - continue; - } - - int mid = a.constants.getMultinameId(m, abc.constants); - if (mid > 0) { - usages.addAll(a.findMultinameDefinition(mid)); - } - } - } - - //more than one? display list - if (!usages.isEmpty()) { - return true; - } - } - - return decompiledTextArea.getLocalDeclarationOfPos(pos, new Reference<>(null)) != -1; - } - - private void gotoDeclaration(int pos) { - Reference abcIndex = new Reference<>(0); - Reference classIndex = new Reference<>(0); - Reference traitIndex = new Reference<>(0); - Reference classTrait = new Reference<>(false); - Reference multinameIndexRef = new Reference<>(0); - - if (decompiledTextArea.getPropertyTypeAtPos(pos, abcIndex, classIndex, traitIndex, classTrait, multinameIndexRef)) { - UsageFrame.gotoUsage(ABCPanel.this, new TraitMultinameUsage(getAbcList().get(abcIndex.getVal()).getABC(), multinameIndexRef.getVal(), classIndex.getVal(), traitIndex.getVal(), classTrait.getVal(), null, -1) { - }); - return; - } - int multinameIndex = decompiledTextArea.getMultinameAtPos(pos); - if (multinameIndex > -1) { - List usages = abc.findMultinameDefinition(multinameIndex); - - Multiname m = abc.constants.getMultiname(multinameIndex); - //search other ABC tags if this is not private multiname - if (m.getSingleNamespaceIndex(abc.constants) > 0 && m.getSingleNamespace(abc.constants).kind != Namespace.KIND_PRIVATE) { - for (ABCContainerTag at : getAbcList()) { - ABC a = at.getABC(); - if (a == abc) { - continue; - } - int mid = a.constants.getMultinameId(m, abc.constants); - if (mid > 0) { - usages.addAll(a.findMultinameDefinition(mid)); - } - } - } - - //more than one? display list - if (usages.size() > 1) { - UsageFrame usageFrame = new UsageFrame(abc, multinameIndex, ABCPanel.this, true); - usageFrame.setVisible(true); - return; - } else if (!usages.isEmpty()) { //one - UsageFrame.gotoUsage(ABCPanel.this, usages.get(0)); - return; - } - } - - int dpos = decompiledTextArea.getLocalDeclarationOfPos(pos, new Reference<>(null)); - if (dpos > -1) { - decompiledTextArea.setCaretPosition(dpos); - - } - - } - - private class CtrlClickHandler extends KeyAdapter implements MouseListener, MouseMotionListener { - - private boolean ctrlDown = false; - - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == 17 && !decompiledTextArea.isEditable()) { - ctrlDown = true; - //decompiledTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } - } - - @Override - public void keyReleased(KeyEvent e) { - if (e.getKeyCode() == 17) { - ctrlDown = false; - //decompiledTextArea.setCursor(Cursor.getDefaultCursor()); - } - } - - @Override - public void mouseClicked(MouseEvent e) { - if (ctrlDown && e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1 && !decompiledTextArea.isEditable()) { - ctrlDown = false; - //decompiledTextArea.setCursor(Cursor.getDefaultCursor()); - //gotoDeclaration(); - } - } - - @Override - public void mousePressed(MouseEvent e) { - - } - - @Override - public void mouseReleased(MouseEvent e) { - } - - @Override - public void mouseEntered(MouseEvent e) { - } - - @Override - public void mouseExited(MouseEvent e) { - ctrlDown = false; - decompiledTextArea.setCursor(Cursor.getDefaultCursor()); - } - - @Override - public void mouseDragged(MouseEvent e) { - } - - @Override - public void mouseMoved(MouseEvent e) { - if (ctrlDown && decompiledTextArea.isEditable()) { - ctrlDown = false; - decompiledTextArea.setCursor(Cursor.getDefaultCursor()); - } - } - } - - public void reload() { - lastDecompiled = ""; - SWF swf = getSwf(); - if (swf != null) { - swf.clearScriptCache(); - } - - decompiledTextArea.reloadClass(); - detailPanel.methodTraitPanel.methodCodePanel.clear(); - } - - @Override - public void itemStateChanged(ItemEvent e) { - if (e.getSource() == constantTypeList) { - int index = ((JComboBox) e.getSource()).getSelectedIndex(); - if (index == -1) { - return; - } - updateConstList(); - } - } - - public void display() { - setVisible(true); - } - - public void hilightScript(SWF swf, String name) { - TagTreeModel ttm = (TagTreeModel) mainPanel.tagTree.getModel(); - TreeItem scriptsNode = ttm.getScriptsNode(swf); - if (scriptsNode instanceof ClassesListTreeModel) { - ClassesListTreeModel clModel = (ClassesListTreeModel) scriptsNode; - ScriptPack pack = null; - for (ScriptPack item : clModel.getList()) { - if (!item.isSimple && Configuration.ignoreCLikePackages.get()) { - continue; - } - ClassPath classPath = item.getClassPath(); - - // first check the className to avoid calling unnecessary toString - if (name.endsWith(classPath.className) && classPath.toRawString().equals(name)) { - pack = item; - break; - } - } - if (pack != null) { - hilightScript(pack); - } - } - } - - public void hilightScript(ScriptPack pack) { - TagTreeModel ttm = (TagTreeModel) mainPanel.tagTree.getModel(); - TreePath tp0 = ttm.getTreePath(pack); - if (tp0 == null) { - mainPanel.closeTagTreeSearch(); - tp0 = ttm.getTreePath(pack); - } - final TreePath tp = tp0; - View.execInEventDispatchLater(() -> { - mainPanel.tagTree.setSelectionPath(tp); - mainPanel.tagTree.scrollPathToVisible(tp); - }); - - } - - @Override - public void updateSearchPos(ABCPanelSearchResult item) { - ScriptPack pack = item.getScriptPack(); - setAbc(pack.abc); - decompiledTextArea.setScript(pack, false); - hilightScript(pack); - decompiledTextArea.setCaretPosition(0); - - View.execInEventDispatchLater(() -> { - searchPanel.showQuickFindDialog(decompiledTextArea); - }); - - } - - public boolean isDirectEditing() { - return saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled(); - } - - public void setDecompiledEditMode(boolean val) { - View.execInEventDispatch(new Runnable() { - - @Override - public void run() { - if (val) { - lastDecompiled = decompiledTextArea.getText(); - } else { - decompiledTextArea.setText(lastDecompiled); - } - - decompiledTextArea.setEditable(val); - saveDecompiledButton.setVisible(val); - saveDecompiledButton.setEnabled(false); - editDecompiledButton.setVisible(!val); - experimentalLabel.setVisible(!val); - cancelDecompiledButton.setVisible(val); - decompiledTextArea.getCaret().setVisible(true); - decLabel.setIcon(val ? View.getIcon("editing16") : null); - detailPanel.setVisible(!val); - - decompiledTextArea.ignoreCarret = val; - decompiledTextArea.requestFocusInWindow(); - } - }); - - } - - private void editDecompiledButtonActionPerformed(ActionEvent evt) { - File swc = Configuration.getPlayerSWC(); - if (swc == null || !swc.exists()) { - if (View.showConfirmDialog(this, AppStrings.translate("message.playerpath.lib.notset"), AppStrings.translate("message.action.playerglobal.title"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE) == JOptionPane.OK_OPTION) { - Main.advancedSettings("paths"); - return; - } - } - if (View.showConfirmDialog(null, AppStrings.translate("message.confirm.experimental.function"), AppStrings.translate("message.warning"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, Configuration.warningExperimentalAS3Edit, JOptionPane.OK_OPTION) == JOptionPane.OK_OPTION) { - setDecompiledEditMode(true); - } - } - - private void cancelDecompiledButtonActionPerformed(ActionEvent evt) { - setDecompiledEditMode(false); - } - - private void saveDecompiledButtonActionPerformed(ActionEvent evt) { - ScriptPack pack = decompiledTextArea.getScriptLeaf(); - int oldIndex = pack.scriptIndex; - SWF.uncache(pack); - - try { - String oldSp = pack.getClassPath().toRawString(); - /*List packs = abc.script_info.get(oldIndex).getPacks(abc, oldIndex, null, pack.allABCs); - if (!packs.isEmpty()) { - - }*/ - - String as = decompiledTextArea.getText(); - abc.replaceScriptPack(pack, as); - lastDecompiled = as; - setDecompiledEditMode(false); - mainPanel.updateClassesList(); - - if (oldSp != null) { - hilightScript(getSwf(), oldSp); - } - - reload(); - View.showMessageDialog(this, AppStrings.translate("message.action.saved"), AppStrings.translate("dialog.message.title"), JOptionPane.INFORMATION_MESSAGE, Configuration.showCodeSavedMessage); - } catch (AVM2ParseException ex) { - abc.script_info.get(oldIndex).delete(abc, false); - decompiledTextArea.gotoLine((int) ex.line); - decompiledTextArea.markError(); - View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } catch (CompilationException ex) { - abc.script_info.get(oldIndex).delete(abc, false); - decompiledTextArea.gotoLine((int) ex.line); - decompiledTextArea.markError(); - View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - - } catch (Throwable ex) { - Logger.getLogger(ABCPanel.class - .getName()).log(Level.SEVERE, null, ex); - } - } - - private void addTraitButtonActionPerformed(ActionEvent evt) { - int class_index = decompiledTextArea.getClassIndex(); - if (class_index < 0) { - return; - } - if (newTraitDialog == null) { - newTraitDialog = new NewTraitDialog(); - } - int void_type = abc.constants.getPublicQnameId("void", true);//abc.constants.forceGetMultinameId(new Multiname(Multiname.QNAME, abc.constants.forceGetStringId("void"), abc.constants.forceGetNamespaceId(new Namespace(Namespace.KIND_PACKAGE, abc.constants.forceGetStringId("")), 0), -1, -1, new ArrayList())); - int int_type = abc.constants.getPublicQnameId("int", true); //abc.constants.forceGetMultinameId(new Multiname(Multiname.QNAME, abc.constants.forceGetStringId("int"), abc.constants.forceGetNamespaceId(new Namespace(Namespace.KIND_PACKAGE, abc.constants.forceGetStringId("")), 0), -1, -1, new ArrayList())); - - Trait t = null; - int kind; - int nskind; - String name = null; - boolean isStatic; - Multiname m; - - boolean again = false; - loopm: - do { - if (again) { - View.showMessageDialog(null, AppStrings.translate("error.trait.exists").replace("%name%", name), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } - again = false; - if (newTraitDialog.showDialog() != AppDialog.OK_OPTION) { - return; - } - kind = newTraitDialog.getTraitType(); - nskind = newTraitDialog.getNamespaceKind(); - name = newTraitDialog.getTraitName(); - isStatic = newTraitDialog.getStatic(); - m = Multiname.createQName(false, abc.constants.getStringId(name, true), abc.constants.getNamespaceId(nskind, "", 0, true)); - int mid = abc.constants.getMultinameId(m, false); - if (mid <= 0) { - break; - } - for (Trait tr : abc.class_info.get(class_index).static_traits.traits) { - if (tr.name_index == mid) { - again = true; - break; - } - } - - for (Trait tr : abc.instance_info.get(class_index).instance_traits.traits) { - if (tr.name_index == mid) { - again = true; - break; - } - } - } while (again); - switch (kind) { - case Trait.TRAIT_GETTER: - case Trait.TRAIT_SETTER: - case Trait.TRAIT_METHOD: - TraitMethodGetterSetter tm = new TraitMethodGetterSetter(); - MethodInfo mi = new MethodInfo(new int[0], void_type, abc.constants.getStringId(name, true), 0, new ValueKind[0], new int[0]); - int method_info = abc.addMethodInfo(mi); - tm.method_info = method_info; - MethodBody body = new MethodBody(abc, new Traits(), new byte[0], new ABCException[0]); - body.method_info = method_info; - body.init_scope_depth = 1; - body.max_regs = 1; - body.max_scope_depth = 1; - body.max_stack = 1; - body.exceptions = new ABCException[0]; - AVM2Code code = new AVM2Code(); - code.code.add(new AVM2Instruction(0, AVM2Instructions.GetLocal0, null)); - code.code.add(new AVM2Instruction(0, AVM2Instructions.PushScope, null)); - code.code.add(new AVM2Instruction(0, AVM2Instructions.ReturnVoid, null)); - body.setCode(code); - Traits traits = new Traits(); - traits.traits = new ArrayList<>(); - body.traits = traits; - abc.addMethodBody(body); - t = tm; - break; - case Trait.TRAIT_SLOT: - case Trait.TRAIT_CONST: - TraitSlotConst ts = new TraitSlotConst(); - ts.type_index = int_type; - ts.value_kind = ValueKind.CONSTANT_Int; - ts.value_index = abc.constants.getIntId(0, true); - t = ts; - break; - } - if (t != null) { - t.kindType = kind; - t.name_index = abc.constants.getMultinameId(m, true); - int traitId; - if (isStatic) { - traitId = abc.class_info.get(class_index).static_traits.addTrait(t); - } else { - traitId = abc.class_info.get(class_index).static_traits.traits.size() + abc.instance_info.get(class_index).instance_traits.addTrait(t); - } - int scriptIndex = decompiledTextArea.getScriptLeaf().scriptIndex; - if (scriptIndex >= 0 && scriptIndex < abc.script_info.size()) { - abc.script_info.get(scriptIndex).setModified(true); - } - ((Tag) abc.parentTag).setModified(true); - reload(); - decompiledTextArea.gotoTrait(traitId); - } - } - - @Override - public boolean tryAutoSave() { - // todo: implement - return false; - } - - @Override - public boolean isEditing() { - return detailPanel.isEditing() || isModified(); - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.abc; + +import com.jpexs.debugger.flash.Variable; +import com.jpexs.debugger.flash.VariableFlags; +import com.jpexs.debugger.flash.VariableType; +import com.jpexs.debugger.flash.messages.in.InGetVariable; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.ClassPath; +import com.jpexs.decompiler.flash.abc.ScriptPack; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +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.parser.AVM2ParseException; +import com.jpexs.decompiler.flash.abc.avm2.parser.script.Reference; +import com.jpexs.decompiler.flash.abc.types.ABCException; +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.ValueKind; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +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.abc.usages.MultinameUsage; +import com.jpexs.decompiler.flash.abc.usages.TraitMultinameUsage; +import com.jpexs.decompiler.flash.action.parser.ActionParseException; +import com.jpexs.decompiler.flash.action.parser.script.ActionScriptLexer; +import com.jpexs.decompiler.flash.action.parser.script.ParsedSymbol; +import com.jpexs.decompiler.flash.action.parser.script.SymbolType; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.ecma.EcmaScript; +import com.jpexs.decompiler.flash.gui.AppDialog; +import com.jpexs.decompiler.flash.gui.AppStrings; +import com.jpexs.decompiler.flash.gui.DebugPanel; +import com.jpexs.decompiler.flash.gui.DebuggerHandler; +import com.jpexs.decompiler.flash.gui.HeaderLabel; +import com.jpexs.decompiler.flash.gui.Main; +import com.jpexs.decompiler.flash.gui.MainPanel; +import com.jpexs.decompiler.flash.gui.SearchListener; +import com.jpexs.decompiler.flash.gui.SearchPanel; +import com.jpexs.decompiler.flash.gui.TagEditorPanel; +import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.gui.abc.tablemodels.DecimalTableModel; +import com.jpexs.decompiler.flash.gui.abc.tablemodels.DoubleTableModel; +import com.jpexs.decompiler.flash.gui.abc.tablemodels.IntTableModel; +import com.jpexs.decompiler.flash.gui.abc.tablemodels.MultinameTableModel; +import com.jpexs.decompiler.flash.gui.abc.tablemodels.NamespaceSetTableModel; +import com.jpexs.decompiler.flash.gui.abc.tablemodels.NamespaceTableModel; +import com.jpexs.decompiler.flash.gui.abc.tablemodels.StringTableModel; +import com.jpexs.decompiler.flash.gui.abc.tablemodels.UIntTableModel; +import com.jpexs.decompiler.flash.gui.controls.JPersistentSplitPane; +import com.jpexs.decompiler.flash.gui.editor.LinkHandler; +import com.jpexs.decompiler.flash.gui.tagtree.TagTreeModel; +import com.jpexs.decompiler.flash.tags.ABCContainerTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.treeitems.TreeItem; +import com.jpexs.decompiler.graph.CompilationException; +import com.jpexs.helpers.CancellableWorker; +import com.jpexs.helpers.Helper; +import de.hameister.treetable.MyTreeTable; +import de.hameister.treetable.MyTreeTableModel; +import java.awt.BorderLayout; +import java.awt.Cursor; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import javax.swing.AbstractAction; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.JToggleButton; +import javax.swing.SwingConstants; +import javax.swing.border.BevelBorder; +import javax.swing.event.EventListenerList; +import javax.swing.event.TableModelListener; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.table.DefaultTableModel; +import javax.swing.text.Highlighter; +import javax.swing.tree.TreePath; +import jsyntaxpane.SyntaxDocument; +import jsyntaxpane.Token; +import jsyntaxpane.TokenType; + +/** + * + * @author JPEXS + */ +public class ABCPanel extends JPanel implements ItemListener, SearchListener, TagEditorPanel { + + private final MainPanel mainPanel; + + public final TraitsList navigator; + + public ABC abc; + + public final DecompiledEditorPane decompiledTextArea; + + public final JScrollPane decompiledScrollPane; + + private final JPersistentSplitPane splitPane; + + private final JTable constantTable; + + public JComboBox constantTypeList; + + public JLabel asmLabel = new HeaderLabel(AppStrings.translate("panel.disassembled")); + + public JLabel decLabel = new HeaderLabel(AppStrings.translate("panel.decompiled")); + + public final DetailPanel detailPanel; + + private final JPanel navPanel; + + public final JTabbedPane tabbedPane; + + public final SearchPanel searchPanel; + + private NewTraitDialog newTraitDialog; + + public final JLabel scriptNameLabel; + + private final DebugPanel debugPanel; + + private final JLabel experimentalLabel = new JLabel(AppStrings.translate("action.edit.experimental")); + + private final JButton editDecompiledButton = new JButton(AppStrings.translate("button.edit"), View.getIcon("edit16")); + + private final JButton saveDecompiledButton = new JButton(AppStrings.translate("button.save"), View.getIcon("save16")); + + private final JButton cancelDecompiledButton = new JButton(AppStrings.translate("button.cancel"), View.getIcon("cancel16")); + + private String lastDecompiled = null; + + public MainPanel getMainPanel() { + return mainPanel; + } + + public List search(final SWF swf, final String txt, boolean ignoreCase, boolean regexp, CancellableWorker worker) { + List ignoredClasses = new ArrayList<>(); + List ignoredNss = new ArrayList<>(); + + if (Configuration._ignoreAdditionalFlexClasses.get()) { + abc.getSwf().getFlexMainClass(ignoredClasses, ignoredNss); + } + if (txt != null && !txt.isEmpty()) { + searchPanel.setOptions(ignoreCase, regexp); + TagTreeModel ttm = (TagTreeModel) mainPanel.tagTree.getModel(); + TreeItem scriptsNode = ttm.getScriptsNode(swf); + final List found = new ArrayList<>(); + if (scriptsNode instanceof ClassesListTreeModel) { + ClassesListTreeModel clModel = (ClassesListTreeModel) scriptsNode; + List allpacks = clModel.getList(); + final Pattern pat = regexp + ? Pattern.compile(txt, ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0) + : Pattern.compile(Pattern.quote(txt), ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0); + int pos = 0; + loop: + for (final ScriptPack pack : allpacks) { + pos++; + if (!pack.isSimple && Configuration.ignoreCLikePackages.get()) { + continue; + } + if (Configuration._ignoreAdditionalFlexClasses.get()) { + String fullName = pack.getClassPath().packageStr.add(pack.getClassPath().className).toRawString(); + if (ignoredClasses.contains(fullName)) { + continue; + } + for (String ns : ignoredNss) { + if (fullName.startsWith(ns + ".")) { + continue loop; + } + } + } + + String workText = AppStrings.translate("work.searching"); + String decAdd = ""; + if (!SWF.isCached(pack)) { + decAdd = ", " + AppStrings.translate("work.decompiling"); + } + + Main.startWork(workText + " \"" + txt + "\"" + decAdd + " - (" + pos + "/" + allpacks.size() + ") " + pack.getClassPath().toString() + "... ", worker); + try { + if (pat.matcher(SWF.getCached(pack).text).find()) { + ABCPanelSearchResult searchResult = new ABCPanelSearchResult(pack); + found.add(searchResult); + } + } catch (InterruptedException ex) { + break; + } + } + } + + return found; + } + + return null; + } + + public void setAbc(ABC abc) { + if (abc == this.abc) { + return; + } + this.abc = abc; + setDecompiledEditMode(false); + navigator.setAbc(abc); + updateConstList(); + } + + public void updateConstList() { + switch (constantTypeList.getSelectedIndex()) { + case 0: + View.autoResizeColWidth(constantTable, new UIntTableModel(abc)); + break; + case 1: + View.autoResizeColWidth(constantTable, new IntTableModel(abc)); + break; + case 2: + View.autoResizeColWidth(constantTable, new DoubleTableModel(abc)); + break; + case 3: + View.autoResizeColWidth(constantTable, new DecimalTableModel(abc)); + break; + case 4: + View.autoResizeColWidth(constantTable, new StringTableModel(abc)); + break; + case 5: + View.autoResizeColWidth(constantTable, new NamespaceTableModel(abc)); + break; + case 6: + View.autoResizeColWidth(constantTable, new NamespaceSetTableModel(abc)); + break; + case 7: + View.autoResizeColWidth(constantTable, new MultinameTableModel(abc)); + break; + } + //DefaultTableColumnModel colModel = (DefaultTableColumnModel) constantTable.getColumnModel(); + //colModel.getColumn(0).setMaxWidth(50); + } + + public SWF getSwf() { + return abc == null ? null : abc.getSwf(); + } + + public List getAbcList() { + SWF swf = getSwf(); + return swf == null ? null : swf.getAbcList(); + } + + public void clearSwf() { + this.abc = null; + constantTable.setModel(new DefaultTableModel()); + navigator.clearAbc(); + decompiledTextArea.clearScript(); + } + + public static class VariableNode { + + public List path = new ArrayList<>(); + + public Long parentId; + + public int level; + + public Variable thisVar; + + public Variable thisTrait; + + public long thisTraitId; + + private List childs; + + private List childTraits; + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + Objects.hashCode(this.parentId); + hash = 53 * hash + (this.thisVar == null ? 0 : Objects.hashCode(this.thisVar.name)); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final VariableNode other = (VariableNode) obj; + if (!Objects.equals(this.parentId, other.parentId)) { + return false; + } + if (this.thisVar == null && other.thisVar == null) { + return true; + } + if (this.thisVar == null) { + return false; + } + if (other.thisVar == null) { + return false; + } + return Objects.equals(this.thisVar.name, other.thisVar.name); + } + + public boolean loaded = false; + + private static boolean isTraits(Variable v) { + return (v.vType == VariableType.UNKNOWN && "traits".equals(v.typeName)); + } + + @Override + public String toString() { + if (level == 0) { + return "root"; //TODO: localize? + } + return thisVar.name; + } + + private void refresh() { + if (path.size() > 1) { + path.get(path.size() - 2).reloadChildren(); + } else { + //Main.getDebugHandler().refreshFrame(); + //InFrame fr = Main.getDebugHandler().getFrame(); + } + } + + private void reloadChildren() { + InGetVariable igv = Main.getDebugHandler().getVariable(parentId, thisVar.name, true); + childs = new ArrayList<>(); + childTraits = new ArrayList<>(); + if (thisVar.vType != VariableType.FUNCTION || ((thisVar.flags & VariableFlags.HAS_GETTER) > 0)) { + thisVar = igv.parent; + } + Variable curTrait = null; + + for (int i = 0; i < igv.childs.size(); i++) { + if (!isTraits(igv.childs.get(i))) { + childs.add(igv.childs.get(i)); + childTraits.add(curTrait); + } else { + curTrait = igv.childs.get(i); + } + } + } + + private void ensureLoaded() { + if (!loaded) { + reloadChildren(); + loaded = true; + } + } + + public VariableNode getChildAt(int index) { + ensureLoaded(); + Long parId = 0L; + if (thisVar != null && (thisVar.vType == VariableType.OBJECT || thisVar.vType == VariableType.MOVIECLIP)) { + parId = (Long) thisVar.value; + } + VariableNode vn = new VariableNode(level + 1, childs.get(index), parId, childTraits.get(index)); + vn.path.addAll(path); + vn.path.add(vn); + return vn; + } + + public int getChildCount() { + ensureLoaded(); + return childs.size(); + } + + public VariableNode(int level, Variable thisVar, Long parentId, Variable thisTrait) { + this.parentId = parentId; + this.thisVar = thisVar; + this.level = level; + this.thisTrait = thisTrait; + } + + public VariableNode(int level, Variable thisVar, Long parentId, Variable thisTrait, Long thisTraitId, List vars, List varTraits) { + this.parentId = parentId; + + this.thisVar = thisVar; + + this.level = level; + this.childs = vars; + + this.thisTrait = thisTrait; + + this.childTraits = varTraits; + this.path.add(this); + + loaded = true; + } + } + + public static class VariablesTableModel implements MyTreeTableModel { + + List tableListeners = new ArrayList<>(); + + VariableNode root; + + private final Map> nodeCache = new HashMap<>(); + + protected EventListenerList listenerList = new EventListenerList(); + + private static final int CHANGED = 0; + + private static final int INSERTED = 1; + + private static final int REMOVED = 2; + + private static final int STRUCTURE_CHANGED = 3; + + private final MyTreeTable ttable; + + public VariablesTableModel(MyTreeTable ttable, List vars, List parentIds) { + this.ttable = ttable; + List varTraits = new ArrayList<>(); + for (int i = 0; i < vars.size(); i++) { + varTraits.add(null); + } + root = new VariableNode(0, null, 0L, null, 0L, vars, varTraits); + } + + @Override + public int getColumnCount() { + return 3; + } + + @Override + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: + return AppStrings.translate("variables.column.name"); + case 1: + return AppStrings.translate("variables.column.type"); + case 2: + return AppStrings.translate("variables.column.value"); + default: + return null; + } + } + + @Override + public Class getColumnClass(int columnIndex) { + if (columnIndex == 0) { + return MyTreeTableModel.class; + } + return String.class; + } + + @Override + public Object getValueAt(Object node, int columnIndex) { + if (node == root) { + if (columnIndex == 0) { + return "root"; + } + return ""; + } + Variable v = ((VariableNode) node).thisVar; + + switch (columnIndex) { + case 0: + return v.name; + case 1: + String typeStr = v.getTypeAsStr(); + if ("Object".equals(typeStr)) { + typeStr = v.className; + } + if ("Object".equals(typeStr)) { + typeStr = v.typeName; + } + return typeStr; + case 2: + switch (v.vType) { + case VariableType.OBJECT: + case VariableType.MOVIECLIP: + case VariableType.FUNCTION: + return v.getTypeAsStr() + "(" + v.value + ")"; + case VariableType.STRING: + return "\"" + Helper.escapeActionScriptString("" + v.value) + "\""; + default: + return EcmaScript.toString(v.value); + } + + } + return null; + } + + @Override + public boolean isCellEditable(Object node, int column) { + return column == 0 || (column == 2 && node != root && ((VariableNode) node).thisVar.isPrimitive); + } + + @Override + public void setValueAt(Object aValue, Object node, int column) { + ActionScriptLexer lexer = new ActionScriptLexer(new StringReader("" + aValue)); + ParsedSymbol symb; + try { + symb = lexer.lex(); + ParsedSymbol f = lexer.yylex(); + if (f.type != SymbolType.EOF) { + return; + } + } catch (IOException | ActionParseException ex) { + return; + } + int valType; + switch (symb.type) { + case DOUBLE: + valType = VariableType.NUMBER; + break; + case INTEGER: + valType = VariableType.NUMBER; + break; + case NULL: + valType = VariableType.NULL; + break; + case STRING: + valType = VariableType.STRING; + break; + case UNDEFINED: + valType = VariableType.UNDEFINED; + break; + default: + return; + } + Main.getDebugHandler().setVariable(((VariableNode) node).parentId, ((VariableNode) node).thisVar.name, valType, symb.value); + //((VariableNode) node).refresh(); + Object[] path = new Object[((VariableNode) node).path.size()]; + for (int i = 0; i < path.length; i++) { + path[i] = ((VariableNode) node).path.get(i); + } + valueForPathChanged(new TreePath(path), aValue); + //fireTreeNodesChanged(this, path, new int[0]/*removed*/, new Object[]{node}); + } + + @Override + public Object getRoot() { + return root; + } + + @Override + public Object getChild(Object parent, int index) { + return ((VariableNode) parent).getChildAt(index); + } + + @Override + public int getChildCount(Object parent) { + int cnt = ((VariableNode) parent).getChildCount(); + return cnt; + } + + @Override + public boolean isLeaf(Object node) { + return getChildCount(node) == 0; + } + + @Override + public void valueForPathChanged(TreePath path, Object newValue) { + fireTreeNodesChanged(ttable, path.getParentPath().getPath(), new int[0], new Object[]{path.getLastPathComponent()}); + } + + @Override + public int getIndexOfChild(Object parent, Object child) { + int cnt = getChildCount(parent); + for (int i = 0; i < cnt; i++) { + if (getChild(parent, i) == child) { + return i; + } + } + return -1; + } + + @Override + public void addTreeModelListener(TreeModelListener l) { + listenerList.add(TreeModelListener.class, l); + } + + @Override + public void removeTreeModelListener(TreeModelListener l) { + listenerList.remove(TreeModelListener.class, l); + } + + protected void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) { + fireTreeNode(CHANGED, source, path, childIndices, children); + } + + protected void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { + fireTreeNode(INSERTED, source, path, childIndices, children); + } + + protected void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { + fireTreeNode(REMOVED, source, path, childIndices, children); + } + + protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { + fireTreeNode(STRUCTURE_CHANGED, source, path, childIndices, children); + } + + private void fireTreeNode(int changeType, Object source, Object[] path, int[] childIndices, Object[] children) { + Object[] listeners = listenerList.getListenerList(); + TreeModelEvent e = new TreeModelEvent(source, path, childIndices, children); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == TreeModelListener.class) { + + switch (changeType) { + case CHANGED: + ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e); + break; + case INSERTED: + ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e); + break; + case REMOVED: + ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e); + break; + case STRUCTURE_CHANGED: + ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e); + break; + default: + break; + } + + } + } + } + } + + public ABCPanel(MainPanel mainPanel) { + + this.mainPanel = mainPanel; + setLayout(new BorderLayout()); + + decompiledTextArea = new DecompiledEditorPane(this); + decompiledTextArea.addTextChangedListener(this::decompiledTextAreaTextChanged); + + decompiledTextArea.setLinkHandler(new LinkHandler() { + + @Override + public boolean isLink(Token token) { + return hasDeclaration(token.start); + } + + @Override + public void handleLink(Token token) { + gotoDeclaration(token.start); + } + + @Override + public Highlighter.HighlightPainter linkPainter() { + return decompiledTextArea.linkPainter(); + } + }); + + searchPanel = new SearchPanel<>(new FlowLayout(), this); + + decompiledScrollPane = new JScrollPane(decompiledTextArea); + + JPanel iconDecPanel = new JPanel(); + iconDecPanel.setLayout(new BoxLayout(iconDecPanel, BoxLayout.Y_AXIS)); + JPanel iconsPanel = new JPanel(); + iconsPanel.setLayout(new BoxLayout(iconsPanel, BoxLayout.X_AXIS)); + + JButton newTraitButton = new JButton(View.getIcon("traitadd16")); + newTraitButton.setMargin(new Insets(5, 5, 5, 5)); + newTraitButton.addActionListener(this::addTraitButtonActionPerformed); + newTraitButton.setToolTipText(AppStrings.translate("button.addtrait")); + iconsPanel.add(newTraitButton); + + scriptNameLabel = new JLabel("-"); + scriptNameLabel.setAlignmentX(0); + iconsPanel.setAlignmentX(0); + decompiledScrollPane.setAlignmentX(0); + iconDecPanel.add(scriptNameLabel); + iconDecPanel.add(iconsPanel); + iconDecPanel.add(decompiledScrollPane); + + final JPanel decButtonsPan = new JPanel(new FlowLayout()); + decButtonsPan.setBorder(new BevelBorder(BevelBorder.RAISED)); + decButtonsPan.add(editDecompiledButton); + decButtonsPan.add(experimentalLabel); + decButtonsPan.add(saveDecompiledButton); + decButtonsPan.add(cancelDecompiledButton); + + editDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); + saveDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); + cancelDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); + + saveDecompiledButton.addActionListener(this::saveDecompiledButtonActionPerformed); + editDecompiledButton.addActionListener(this::editDecompiledButtonActionPerformed); + cancelDecompiledButton.addActionListener(this::cancelDecompiledButtonActionPerformed); + + saveDecompiledButton.setVisible(false); + cancelDecompiledButton.setVisible(false); + decButtonsPan.setAlignmentX(0); + + JPanel decPanel = new JPanel(new BorderLayout()); + decPanel.add(searchPanel, BorderLayout.NORTH); + decPanel.add(iconDecPanel, BorderLayout.CENTER); + decPanel.add(decButtonsPan, BorderLayout.SOUTH); + detailPanel = new DetailPanel(this); + JPanel panB = new JPanel(); + panB.setLayout(new BorderLayout()); + panB.add(decLabel, BorderLayout.NORTH); + + Main.getDebugHandler().addConnectionListener(new DebuggerHandler.ConnectionListener() { + + @Override + public void connected() { + decButtonsPan.setVisible(false); + } + + @Override + public void disconnected() { + decButtonsPan.setVisible(true); + } + }); + + debugPanel = new DebugPanel(); + + JPersistentSplitPane sp2; + + panB.add(sp2 = new JPersistentSplitPane(JSplitPane.VERTICAL_SPLIT, decPanel, debugPanel, Configuration.guiAvm2VarsSplitPaneDividerLocationPercent), BorderLayout.CENTER); + sp2.setContinuousLayout(true); + + debugPanel.setVisible(false); + + decLabel.setHorizontalAlignment(SwingConstants.CENTER); + //decLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); + splitPane = new JPersistentSplitPane(JSplitPane.HORIZONTAL_SPLIT, panB, detailPanel, Configuration.guiAvm2SplitPaneDividerLocationPercent); + splitPane.setContinuousLayout(true); + + decompiledTextArea.changeContentType("text/actionscript"); + decompiledTextArea.setFont(new Font("Monospaced", Font.PLAIN, decompiledTextArea.getFont().getSize())); + + View.addEditorAction(decompiledTextArea, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + int multinameIndex = decompiledTextArea.getMultinameUnderCaret(); + if (multinameIndex > -1) { + UsageFrame usageFrame = new UsageFrame(abc, multinameIndex, ABCPanel.this, false); + usageFrame.setVisible(true); + } + } + }, "find-usages", AppStrings.translate("abc.action.find-usages"), "control U"); + + View.addEditorAction(decompiledTextArea, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + gotoDeclaration(decompiledTextArea.getCaretPosition()); + } + }, "find-declaration", AppStrings.translate("abc.action.find-declaration"), "control B"); + + CtrlClickHandler cch = new CtrlClickHandler(); + decompiledTextArea.addKeyListener(cch); + decompiledTextArea.addMouseListener(cch); + decompiledTextArea.addMouseMotionListener(cch); + + navigator = new TraitsList(this); + + navPanel = new JPanel(new BorderLayout()); + JPanel navIconsPanel = new JPanel(); + navIconsPanel.setLayout(new BoxLayout(navIconsPanel, BoxLayout.X_AXIS)); + final JToggleButton sortButton = new JToggleButton(View.getIcon("sort16")); + sortButton.setMargin(new Insets(3, 3, 3, 3)); + navIconsPanel.add(sortButton); + navPanel.add(navIconsPanel, BorderLayout.SOUTH); + navPanel.add(new JScrollPane(navigator), BorderLayout.CENTER); + sortButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + navigator.setSorted(sortButton.isSelected()); + navigator.updateUI(); + } + }); + + tabbedPane = new JTabbedPane(); + tabbedPane.addTab(AppStrings.translate("traits"), navPanel); + add(splitPane, BorderLayout.CENTER); + + JPanel panConstants = new JPanel(); + panConstants.setLayout(new BorderLayout()); + + constantTypeList = new JComboBox<>(new String[]{"UINT", "INT", "DOUBLE", "DECIMAL", "STRING", "NAMESPACE", "NAMESPACESET", "MULTINAME"}); + constantTable = new JTable(); + if (abc != null) { + View.autoResizeColWidth(constantTable, new UIntTableModel(abc)); + } + constantTable.setAutoCreateRowSorter(true); + + final ABCPanel t = this; + constantTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + if (constantTypeList.getSelectedIndex() == 7) { //MULTINAME + int rowIndex = constantTable.getSelectedRow(); + if (rowIndex == -1) { + return; + } + int multinameIndex = constantTable.convertRowIndexToModel(rowIndex); + if (multinameIndex > 0) { + UsageFrame usageFrame = new UsageFrame(abc, multinameIndex, t, false); + usageFrame.setVisible(true); + } + } + } + } + }); + constantTypeList.addItemListener(this); + panConstants.add(constantTypeList, BorderLayout.NORTH); + panConstants.add(new JScrollPane(constantTable), BorderLayout.CENTER); + tabbedPane.addTab(AppStrings.translate("constants"), panConstants); + } + + private void decompiledTextAreaTextChanged() { + setModified(true); + } + + private boolean isModified() { + return saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled(); + } + + private void setModified(boolean value) { + saveDecompiledButton.setEnabled(value); + cancelDecompiledButton.setEnabled(value); + } + + private boolean hasDeclaration(int pos) { + if (decompiledTextArea == null) { + return false; //? + } + SyntaxDocument sd = (SyntaxDocument) decompiledTextArea.getDocument(); + Token t = sd.getTokenAt(pos + 1); + if (t == null || (t.type != TokenType.IDENTIFIER && t.type != TokenType.KEYWORD && t.type != TokenType.REGEX)) { + return false; + } + Reference abcIndex = new Reference<>(0); + Reference classIndex = new Reference<>(0); + Reference traitIndex = new Reference<>(0); + Reference multinameIndexRef = new Reference<>(0); + Reference classTrait = new Reference<>(false); + + if (decompiledTextArea.getPropertyTypeAtPos(pos, abcIndex, classIndex, traitIndex, classTrait, multinameIndexRef)) { + return true; + } + int multinameIndex = decompiledTextArea.getMultinameAtPos(pos); + if (multinameIndex > -1) { + if (multinameIndex == 0) { + return false; + } + List usages = abc.findMultinameDefinition(multinameIndex); + + Multiname m = abc.constants.getMultiname(multinameIndex); + + //search other ABC tags if this is not private multiname + if (m.getSingleNamespaceIndex(abc.constants) > 0 && abc.constants.getNamespace(m.getSingleNamespaceIndex(abc.constants)).kind != Namespace.KIND_PRIVATE) { + for (ABCContainerTag at : getAbcList()) { + ABC a = at.getABC(); + if (a == abc) { + continue; + } + + int mid = a.constants.getMultinameId(m, abc.constants); + if (mid > 0) { + usages.addAll(a.findMultinameDefinition(mid)); + } + } + } + + //more than one? display list + if (!usages.isEmpty()) { + return true; + } + } + + return decompiledTextArea.getLocalDeclarationOfPos(pos, new Reference<>(null)) != -1; + } + + private void gotoDeclaration(int pos) { + Reference abcIndex = new Reference<>(0); + Reference classIndex = new Reference<>(0); + Reference traitIndex = new Reference<>(0); + Reference classTrait = new Reference<>(false); + Reference multinameIndexRef = new Reference<>(0); + + if (decompiledTextArea.getPropertyTypeAtPos(pos, abcIndex, classIndex, traitIndex, classTrait, multinameIndexRef)) { + UsageFrame.gotoUsage(ABCPanel.this, new TraitMultinameUsage(getAbcList().get(abcIndex.getVal()).getABC(), multinameIndexRef.getVal(), classIndex.getVal(), traitIndex.getVal(), classTrait.getVal(), null, -1) { + }); + return; + } + int multinameIndex = decompiledTextArea.getMultinameAtPos(pos); + if (multinameIndex > -1) { + List usages = abc.findMultinameDefinition(multinameIndex); + + Multiname m = abc.constants.getMultiname(multinameIndex); + //search other ABC tags if this is not private multiname + if (m.getSingleNamespaceIndex(abc.constants) > 0 && m.getSingleNamespace(abc.constants).kind != Namespace.KIND_PRIVATE) { + for (ABCContainerTag at : getAbcList()) { + ABC a = at.getABC(); + if (a == abc) { + continue; + } + int mid = a.constants.getMultinameId(m, abc.constants); + if (mid > 0) { + usages.addAll(a.findMultinameDefinition(mid)); + } + } + } + + //more than one? display list + if (usages.size() > 1) { + UsageFrame usageFrame = new UsageFrame(abc, multinameIndex, ABCPanel.this, true); + usageFrame.setVisible(true); + return; + } else if (!usages.isEmpty()) { //one + UsageFrame.gotoUsage(ABCPanel.this, usages.get(0)); + return; + } + } + + int dpos = decompiledTextArea.getLocalDeclarationOfPos(pos, new Reference<>(null)); + if (dpos > -1) { + decompiledTextArea.setCaretPosition(dpos); + + } + + } + + private class CtrlClickHandler extends KeyAdapter implements MouseListener, MouseMotionListener { + + private boolean ctrlDown = false; + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == 17 && !decompiledTextArea.isEditable()) { + ctrlDown = true; + //decompiledTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + } + + @Override + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == 17) { + ctrlDown = false; + //decompiledTextArea.setCursor(Cursor.getDefaultCursor()); + } + } + + @Override + public void mouseClicked(MouseEvent e) { + if (ctrlDown && e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1 && !decompiledTextArea.isEditable()) { + ctrlDown = false; + //decompiledTextArea.setCursor(Cursor.getDefaultCursor()); + //gotoDeclaration(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + + } + + @Override + public void mouseReleased(MouseEvent e) { + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + ctrlDown = false; + decompiledTextArea.setCursor(Cursor.getDefaultCursor()); + } + + @Override + public void mouseDragged(MouseEvent e) { + } + + @Override + public void mouseMoved(MouseEvent e) { + if (ctrlDown && decompiledTextArea.isEditable()) { + ctrlDown = false; + decompiledTextArea.setCursor(Cursor.getDefaultCursor()); + } + } + } + + public void reload() { + lastDecompiled = ""; + SWF swf = getSwf(); + if (swf != null) { + swf.clearScriptCache(); + } + + decompiledTextArea.reloadClass(); + detailPanel.methodTraitPanel.methodCodePanel.clear(); + } + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getSource() == constantTypeList) { + int index = ((JComboBox) e.getSource()).getSelectedIndex(); + if (index == -1) { + return; + } + updateConstList(); + } + } + + public void display() { + setVisible(true); + } + + public void hilightScript(SWF swf, String name) { + TagTreeModel ttm = (TagTreeModel) mainPanel.tagTree.getModel(); + TreeItem scriptsNode = ttm.getScriptsNode(swf); + if (scriptsNode instanceof ClassesListTreeModel) { + ClassesListTreeModel clModel = (ClassesListTreeModel) scriptsNode; + ScriptPack pack = null; + for (ScriptPack item : clModel.getList()) { + if (!item.isSimple && Configuration.ignoreCLikePackages.get()) { + continue; + } + ClassPath classPath = item.getClassPath(); + + // first check the className to avoid calling unnecessary toString + if (name.endsWith(classPath.className) && classPath.toRawString().equals(name)) { + pack = item; + break; + } + } + if (pack != null) { + hilightScript(pack); + } + } + } + + public void hilightScript(ScriptPack pack) { + TagTreeModel ttm = (TagTreeModel) mainPanel.tagTree.getModel(); + TreePath tp0 = ttm.getTreePath(pack); + if (tp0 == null) { + mainPanel.closeTagTreeSearch(); + tp0 = ttm.getTreePath(pack); + } + final TreePath tp = tp0; + View.execInEventDispatchLater(() -> { + mainPanel.tagTree.setSelectionPath(tp); + mainPanel.tagTree.scrollPathToVisible(tp); + }); + + } + + @Override + public void updateSearchPos(ABCPanelSearchResult item) { + ScriptPack pack = item.getScriptPack(); + setAbc(pack.abc); + decompiledTextArea.setScript(pack, false); + hilightScript(pack); + decompiledTextArea.setCaretPosition(0); + + View.execInEventDispatchLater(() -> { + searchPanel.showQuickFindDialog(decompiledTextArea); + }); + + } + + public boolean isDirectEditing() { + return saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled(); + } + + public void setDecompiledEditMode(boolean val) { + View.execInEventDispatch(new Runnable() { + + @Override + public void run() { + if (val) { + lastDecompiled = decompiledTextArea.getText(); + } else { + decompiledTextArea.setText(lastDecompiled); + } + + decompiledTextArea.setEditable(val); + saveDecompiledButton.setVisible(val); + saveDecompiledButton.setEnabled(false); + editDecompiledButton.setVisible(!val); + experimentalLabel.setVisible(!val); + cancelDecompiledButton.setVisible(val); + decompiledTextArea.getCaret().setVisible(true); + decLabel.setIcon(val ? View.getIcon("editing16") : null); + detailPanel.setVisible(!val); + + decompiledTextArea.ignoreCarret = val; + decompiledTextArea.requestFocusInWindow(); + } + }); + + } + + private void editDecompiledButtonActionPerformed(ActionEvent evt) { + File swc = Configuration.getPlayerSWC(); + if (swc == null || !swc.exists()) { + if (View.showConfirmDialog(this, AppStrings.translate("message.playerpath.lib.notset"), AppStrings.translate("message.action.playerglobal.title"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE) == JOptionPane.OK_OPTION) { + Main.advancedSettings("paths"); + return; + } + } + if (View.showConfirmDialog(null, AppStrings.translate("message.confirm.experimental.function"), AppStrings.translate("message.warning"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, Configuration.warningExperimentalAS3Edit, JOptionPane.OK_OPTION) == JOptionPane.OK_OPTION) { + setDecompiledEditMode(true); + } + } + + private void cancelDecompiledButtonActionPerformed(ActionEvent evt) { + setDecompiledEditMode(false); + } + + private void saveDecompiledButtonActionPerformed(ActionEvent evt) { + ScriptPack pack = decompiledTextArea.getScriptLeaf(); + int oldIndex = pack.scriptIndex; + SWF.uncache(pack); + + try { + String oldSp = pack.getClassPath().toRawString(); + /*List packs = abc.script_info.get(oldIndex).getPacks(abc, oldIndex, null, pack.allABCs); + if (!packs.isEmpty()) { + + }*/ + + String as = decompiledTextArea.getText(); + abc.replaceScriptPack(pack, as); + lastDecompiled = as; + setDecompiledEditMode(false); + mainPanel.updateClassesList(); + + if (oldSp != null) { + hilightScript(getSwf(), oldSp); + } + + reload(); + View.showMessageDialog(this, AppStrings.translate("message.action.saved"), AppStrings.translate("dialog.message.title"), JOptionPane.INFORMATION_MESSAGE, Configuration.showCodeSavedMessage); + } catch (AVM2ParseException ex) { + abc.script_info.get(oldIndex).delete(abc, false); + decompiledTextArea.gotoLine((int) ex.line); + decompiledTextArea.markError(); + View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } catch (CompilationException ex) { + abc.script_info.get(oldIndex).delete(abc, false); + decompiledTextArea.gotoLine((int) ex.line); + decompiledTextArea.markError(); + View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + + } catch (Throwable ex) { + Logger.getLogger(ABCPanel.class + .getName()).log(Level.SEVERE, null, ex); + } + } + + private void addTraitButtonActionPerformed(ActionEvent evt) { + int class_index = decompiledTextArea.getClassIndex(); + if (class_index < 0) { + return; + } + if (newTraitDialog == null) { + newTraitDialog = new NewTraitDialog(); + } + int void_type = abc.constants.getPublicQnameId("void", true);//abc.constants.forceGetMultinameId(new Multiname(Multiname.QNAME, abc.constants.forceGetStringId("void"), abc.constants.forceGetNamespaceId(new Namespace(Namespace.KIND_PACKAGE, abc.constants.forceGetStringId("")), 0), -1, -1, new ArrayList())); + int int_type = abc.constants.getPublicQnameId("int", true); //abc.constants.forceGetMultinameId(new Multiname(Multiname.QNAME, abc.constants.forceGetStringId("int"), abc.constants.forceGetNamespaceId(new Namespace(Namespace.KIND_PACKAGE, abc.constants.forceGetStringId("")), 0), -1, -1, new ArrayList())); + + Trait t = null; + int kind; + int nskind; + String name = null; + boolean isStatic; + Multiname m; + + boolean again = false; + loopm: + do { + if (again) { + View.showMessageDialog(null, AppStrings.translate("error.trait.exists").replace("%name%", name), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + again = false; + if (newTraitDialog.showDialog() != AppDialog.OK_OPTION) { + return; + } + kind = newTraitDialog.getTraitType(); + nskind = newTraitDialog.getNamespaceKind(); + name = newTraitDialog.getTraitName(); + isStatic = newTraitDialog.getStatic(); + m = Multiname.createQName(false, abc.constants.getStringId(name, true), abc.constants.getNamespaceId(nskind, "", 0, true)); + int mid = abc.constants.getMultinameId(m, false); + if (mid <= 0) { + break; + } + for (Trait tr : abc.class_info.get(class_index).static_traits.traits) { + if (tr.name_index == mid) { + again = true; + break; + } + } + + for (Trait tr : abc.instance_info.get(class_index).instance_traits.traits) { + if (tr.name_index == mid) { + again = true; + break; + } + } + } while (again); + switch (kind) { + case Trait.TRAIT_GETTER: + case Trait.TRAIT_SETTER: + case Trait.TRAIT_METHOD: + TraitMethodGetterSetter tm = new TraitMethodGetterSetter(); + MethodInfo mi = new MethodInfo(new int[0], void_type, abc.constants.getStringId(name, true), 0, new ValueKind[0], new int[0]); + int method_info = abc.addMethodInfo(mi); + tm.method_info = method_info; + MethodBody body = new MethodBody(abc, new Traits(), new byte[0], new ABCException[0]); + body.method_info = method_info; + body.init_scope_depth = 1; + body.max_regs = 1; + body.max_scope_depth = 1; + body.max_stack = 1; + body.exceptions = new ABCException[0]; + AVM2Code code = new AVM2Code(); + code.code.add(new AVM2Instruction(0, AVM2Instructions.GetLocal0, null)); + code.code.add(new AVM2Instruction(0, AVM2Instructions.PushScope, null)); + code.code.add(new AVM2Instruction(0, AVM2Instructions.ReturnVoid, null)); + body.setCode(code); + Traits traits = new Traits(); + traits.traits = new ArrayList<>(); + body.traits = traits; + abc.addMethodBody(body); + t = tm; + break; + case Trait.TRAIT_SLOT: + case Trait.TRAIT_CONST: + TraitSlotConst ts = new TraitSlotConst(); + ts.type_index = int_type; + ts.value_kind = ValueKind.CONSTANT_Int; + ts.value_index = abc.constants.getIntId(0, true); + t = ts; + break; + } + if (t != null) { + t.kindType = kind; + t.name_index = abc.constants.getMultinameId(m, true); + int traitId; + if (isStatic) { + traitId = abc.class_info.get(class_index).static_traits.addTrait(t); + } else { + traitId = abc.class_info.get(class_index).static_traits.traits.size() + abc.instance_info.get(class_index).instance_traits.addTrait(t); + } + int scriptIndex = decompiledTextArea.getScriptLeaf().scriptIndex; + if (scriptIndex >= 0 && scriptIndex < abc.script_info.size()) { + abc.script_info.get(scriptIndex).setModified(true); + } + ((Tag) abc.parentTag).setModified(true); + reload(); + decompiledTextArea.gotoTrait(traitId); + } + } + + @Override + public boolean tryAutoSave() { + // todo: implement + return false; + } + + @Override + public boolean isEditing() { + return detailPanel.isEditing() || isModified(); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/abc/NewTraitDialog.java b/src/com/jpexs/decompiler/flash/gui/abc/NewTraitDialog.java index aa0d5a3a2..f40d3cea8 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/NewTraitDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/NewTraitDialog.java @@ -1,184 +1,184 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.abc; - -import com.jpexs.decompiler.flash.abc.types.Namespace; -import com.jpexs.decompiler.flash.abc.types.traits.Trait; -import com.jpexs.decompiler.flash.gui.AppDialog; -import com.jpexs.decompiler.flash.gui.AppStrings; -import com.jpexs.decompiler.flash.gui.View; -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.event.ActionEvent; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextField; -import javax.swing.event.AncestorEvent; -import javax.swing.event.AncestorListener; - -/** - * - * @author JPEXS - */ -public class NewTraitDialog extends AppDialog { - - private static final int modifiers[] = new int[]{ - Namespace.KIND_PACKAGE, - Namespace.KIND_PRIVATE, - Namespace.KIND_PROTECTED, - Namespace.KIND_NAMESPACE, - Namespace.KIND_PACKAGE_INTERNAL, - Namespace.KIND_EXPLICIT, - Namespace.KIND_STATIC_PROTECTED - }; - - private static final int types[] = new int[]{ - Trait.TRAIT_METHOD, - Trait.TRAIT_GETTER, - Trait.TRAIT_SETTER, - Trait.TRAIT_CONST, - Trait.TRAIT_SLOT - }; - - private final JComboBox accessComboBox; - - private final JComboBox typeComboBox; - - private final JCheckBox staticCheckbox; - - private final JTextField nameField; - - private int result = ERROR_OPTION; - - public boolean getStatic() { - return staticCheckbox.isSelected(); - } - - public int getNamespaceKind() { - return modifiers[accessComboBox.getSelectedIndex()]; - } - - public int getTraitType() { - return types[typeComboBox.getSelectedIndex()]; - } - - public String getTraitName() { - return nameField.getText(); - } - - public NewTraitDialog() { - setSize(500, 300); - setTitle(translate("dialog.title")); - View.centerScreen(this); - View.setWindowIcon(this); - Container cnt = getContentPane(); - cnt.setLayout(new BorderLayout()); - JPanel optionsPanel = new JPanel(new FlowLayout()); - //optionsPanel.add(new JLabel(translate("label.type"))); - typeComboBox = new JComboBox<>(new String[]{ - translate("type.method"), - translate("type.getter"), - translate("type.setter"), - translate("type.const"), - translate("type.slot"),}); - staticCheckbox = new JCheckBox(translate("checkbox.static")); - optionsPanel.add(staticCheckbox); - String accessStrings[] = new String[modifiers.length]; - for (int i = 0; i < accessStrings.length; i++) { - String pref = Namespace.kindToPrefix(modifiers[i]); - String name = Namespace.kindToStr(modifiers[i]); - accessStrings[i] = (pref.isEmpty() ? "" : pref + " ") + "(" + name + ")"; - } - - //optionsPanel.add(new JLabel(translate("label.access"))); - accessComboBox = new JComboBox<>(accessStrings); - optionsPanel.add(accessComboBox); - - optionsPanel.add(typeComboBox); - - //optionsPanel.add(new JLabel(translate("label.name"))); - nameField = new JTextField(); - nameField.setPreferredSize(new Dimension(300, nameField.getPreferredSize().height)); - optionsPanel.add(nameField); - - cnt.add(optionsPanel, BorderLayout.CENTER); - JPanel buttonsPanel = new JPanel(new FlowLayout()); - JButton buttonOk = new JButton(AppStrings.translate("button.ok")); - buttonOk.addActionListener(this::okButtonActionPerformed); - JButton buttonCancel = new JButton(AppStrings.translate("button.cancel")); - buttonCancel.addActionListener(this::cancelButtonActionPerformed); - buttonsPanel.add(buttonOk); - buttonsPanel.add(buttonCancel); - cnt.add(buttonsPanel, BorderLayout.SOUTH); - pack(); - setDefaultCloseOperation(HIDE_ON_CLOSE); - setModalityType(ModalityType.APPLICATION_MODAL); - - nameField.addAncestorListener(new AncestorListener() { - @Override - public void ancestorAdded(AncestorEvent event) { - JComponent component = event.getComponent(); - component.requestFocusInWindow(); - } - - @Override - public void ancestorRemoved(AncestorEvent event) { - } - - @Override - public void ancestorMoved(AncestorEvent event) { - } - }); - getRootPane().setDefaultButton(buttonOk); - } - - @Override - public void setVisible(boolean b) { - if (b) { - result = ERROR_OPTION; - nameField.setText(""); - } - - super.setVisible(b); - } - - private void okButtonActionPerformed(ActionEvent evt) { - result = OK_OPTION; - if (nameField.getText().trim().isEmpty()) { - View.showMessageDialog(null, translate("error.name"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - return; - } - - setVisible(false); - } - - private void cancelButtonActionPerformed(ActionEvent evt) { - result = CANCEL_OPTION; - setVisible(false); - } - - public int showDialog() { - setVisible(true); - return result; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.abc; + +import com.jpexs.decompiler.flash.abc.types.Namespace; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +import com.jpexs.decompiler.flash.gui.AppDialog; +import com.jpexs.decompiler.flash.gui.AppStrings; +import com.jpexs.decompiler.flash.gui.View; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.AncestorEvent; +import javax.swing.event.AncestorListener; + +/** + * + * @author JPEXS + */ +public class NewTraitDialog extends AppDialog { + + private static final int[] modifiers = new int[]{ + Namespace.KIND_PACKAGE, + Namespace.KIND_PRIVATE, + Namespace.KIND_PROTECTED, + Namespace.KIND_NAMESPACE, + Namespace.KIND_PACKAGE_INTERNAL, + Namespace.KIND_EXPLICIT, + Namespace.KIND_STATIC_PROTECTED + }; + + private static final int[] types = new int[]{ + Trait.TRAIT_METHOD, + Trait.TRAIT_GETTER, + Trait.TRAIT_SETTER, + Trait.TRAIT_CONST, + Trait.TRAIT_SLOT + }; + + private final JComboBox accessComboBox; + + private final JComboBox typeComboBox; + + private final JCheckBox staticCheckbox; + + private final JTextField nameField; + + private int result = ERROR_OPTION; + + public boolean getStatic() { + return staticCheckbox.isSelected(); + } + + public int getNamespaceKind() { + return modifiers[accessComboBox.getSelectedIndex()]; + } + + public int getTraitType() { + return types[typeComboBox.getSelectedIndex()]; + } + + public String getTraitName() { + return nameField.getText(); + } + + public NewTraitDialog() { + setSize(500, 300); + setTitle(translate("dialog.title")); + View.centerScreen(this); + View.setWindowIcon(this); + Container cnt = getContentPane(); + cnt.setLayout(new BorderLayout()); + JPanel optionsPanel = new JPanel(new FlowLayout()); + //optionsPanel.add(new JLabel(translate("label.type"))); + typeComboBox = new JComboBox<>(new String[]{ + translate("type.method"), + translate("type.getter"), + translate("type.setter"), + translate("type.const"), + translate("type.slot"),}); + staticCheckbox = new JCheckBox(translate("checkbox.static")); + optionsPanel.add(staticCheckbox); + String[] accessStrings = new String[modifiers.length]; + for (int i = 0; i < accessStrings.length; i++) { + String pref = Namespace.kindToPrefix(modifiers[i]); + String name = Namespace.kindToStr(modifiers[i]); + accessStrings[i] = (pref.isEmpty() ? "" : pref + " ") + "(" + name + ")"; + } + + //optionsPanel.add(new JLabel(translate("label.access"))); + accessComboBox = new JComboBox<>(accessStrings); + optionsPanel.add(accessComboBox); + + optionsPanel.add(typeComboBox); + + //optionsPanel.add(new JLabel(translate("label.name"))); + nameField = new JTextField(); + nameField.setPreferredSize(new Dimension(300, nameField.getPreferredSize().height)); + optionsPanel.add(nameField); + + cnt.add(optionsPanel, BorderLayout.CENTER); + JPanel buttonsPanel = new JPanel(new FlowLayout()); + JButton buttonOk = new JButton(AppStrings.translate("button.ok")); + buttonOk.addActionListener(this::okButtonActionPerformed); + JButton buttonCancel = new JButton(AppStrings.translate("button.cancel")); + buttonCancel.addActionListener(this::cancelButtonActionPerformed); + buttonsPanel.add(buttonOk); + buttonsPanel.add(buttonCancel); + cnt.add(buttonsPanel, BorderLayout.SOUTH); + pack(); + setDefaultCloseOperation(HIDE_ON_CLOSE); + setModalityType(ModalityType.APPLICATION_MODAL); + + nameField.addAncestorListener(new AncestorListener() { + @Override + public void ancestorAdded(AncestorEvent event) { + JComponent component = event.getComponent(); + component.requestFocusInWindow(); + } + + @Override + public void ancestorRemoved(AncestorEvent event) { + } + + @Override + public void ancestorMoved(AncestorEvent event) { + } + }); + getRootPane().setDefaultButton(buttonOk); + } + + @Override + public void setVisible(boolean b) { + if (b) { + result = ERROR_OPTION; + nameField.setText(""); + } + + super.setVisible(b); + } + + private void okButtonActionPerformed(ActionEvent evt) { + result = OK_OPTION; + if (nameField.getText().trim().isEmpty()) { + View.showMessageDialog(null, translate("error.name"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + return; + } + + setVisible(false); + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + result = CANCEL_OPTION; + setVisible(false); + } + + public int showDialog() { + setVisible(true); + return result; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/debugger/Debugger.java b/src/com/jpexs/decompiler/flash/gui/debugger/Debugger.java index daeda263e..234bbf1bf 100644 --- a/src/com/jpexs/decompiler/flash/gui/debugger/Debugger.java +++ b/src/com/jpexs/decompiler/flash/gui/debugger/Debugger.java @@ -1,308 +1,308 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.debugger; - -import com.jpexs.helpers.utf8.Utf8Helper; -import java.io.ByteArrayOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.WeakHashMap; - -/** - * - * @author JPEXS - */ -public class Debugger { - - private static final Set listeners = new HashSet<>(); - - public synchronized void addMessageListener(DebugListener l) { - listeners.add(l); - } - - public synchronized void removeMessageListener(DebugListener l) { - listeners.remove(l); - } - - private static class DebugHandler extends Thread { - - private final Socket s; - - private final int serverPort; - - private static int maxid = 0; - - private final int id; - - public boolean finished = false; - - private final Map parameters = new HashMap<>(); - - public static final int MSG_STRING = 0; - - public static final int MSG_LOADER_URL = 1; - - public static final int MSG_LOADER_BYTES = 2; - - public String getParameter(String name, String defValue) { - if (parameters.containsKey(name)) { - return parameters.get(name); - } - return defValue; - } - - public int getVersionMajor() { - return Integer.parseInt(getParameter("debug.version.major", "1")); - } - - public int getVersionMinor() { - return Integer.parseInt(getParameter("debug.version.major", "0")); - } - - public boolean hasMsgType() { - return getVersionMajor() > 1 || getVersionMinor() > 0; - } - - public DebugHandler(int serverPort, Socket s) { - this.s = s; - id = maxid++; - this.serverPort = serverPort; - } - - public void cancel() { - try { - s.close(); - } catch (IOException ex) { - //ignore - } - } - - private int readType(InputStream is) throws IOException { - int type = is.read(); - if (type == -1) { - throw new EOFException(); - } - return type; - } - - private byte[] readBytes(InputStream is) throws IOException { - int len = is.read(); - if (len == -1) { - throw new EOFException(); - } - int len2 = is.read(); - if (len2 == -1) { - throw new EOFException(); - } - int len3 = is.read(); - if (len3 == -1) { - throw new EOFException(); - } - int len4 = is.read(); - if (len4 == -1) { - throw new EOFException(); - } - len = (len << 24) + (len2 << 16) + (len3 << 8) + len4; - byte data[] = new byte[len]; - int cnt; - int off = 0; - while (len > 0 && (cnt = is.read(data, off, len)) > 0) { - len -= cnt; - off += cnt; - } - return data; - } - - private String readString(InputStream is) throws IOException { - int len = is.read(); - if (len == -1) { - throw new EOFException(); - } - int len2 = is.read(); - if (len2 == -1) { - throw new EOFException(); - } - len = (len << 8) + len2; - - byte buf[] = new byte[len]; - for (int i = 0; i < len; i++) { - int rd = is.read(); - if (rd == -1) { - throw new EOFException(); - } - buf[i] = (byte) rd; - } - return new String(buf, Utf8Helper.charset); - } - - @Override - public void run() { - String clientName = Integer.toString(id); - try (InputStream is = s.getInputStream()) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - int c; - do { - c = is.read(); - if (c != 0) { - baos.write(c); - } - - } while (c > 0); - String ret = baos.toString("UTF-8"); - - if (ret.equals("")) { - try (OutputStream os = s.getOutputStream()) { - os.write(("").getBytes("UTF-8")); - } - } else { - if (!ret.isEmpty()) { - String param[] = (ret.contains(";") ? ret.split(";") : new String[]{ret}); - for (String p : param) { - if (p.contains("=")) { - String key = p.substring(0, p.indexOf('=')); - String val = p.substring(p.indexOf('=') + 1); - parameters.put(key, val); - } else { - parameters.put(p, "true"); - } - } - } - boolean hasType = hasMsgType(); - String name = readString(is); - if (!name.isEmpty()) { - clientName = name; - } - while (true) { - int type = 0; - if (hasType) { - type = readType(is); - } - switch (type) { - case MSG_STRING: - ret = readString(is); - for (DebugListener l : listeners) { - l.onMessage(clientName, ret); - } - break; - case MSG_LOADER_URL: - ret = readString(is); - for (DebugListener l : listeners) { - l.onLoaderURL(clientName, ret); - } - break; - case MSG_LOADER_BYTES: - byte retB[] = readBytes(is); - for (DebugListener l : listeners) { - l.onLoaderBytes(clientName, retB); - } - break; - } - } - } - - } catch (IOException ex) { - //ignore - } - try { - s.close(); - } catch (IOException ex) { - //ignore - } - finished = true; - for (DebugListener l : listeners) { - l.onFinish(clientName); - } - } - } - - private static class DebugServerThread extends Thread { - - private final int port; - - private ServerSocket ss; - - private final Map handlers = new WeakHashMap<>(); - - public DebugServerThread(int port) { - this.port = port; - } - - @Override - public void run() { - try { - ss = new ServerSocket(port, 50, InetAddress.getByName("localhost")); - ss.setReuseAddress(true); - while (true) { - Socket s = ss.accept(); - DebugHandler h = new DebugHandler(port, s); - handlers.put(h.id, h); - h.start(); - } - } catch (IOException ex) { - //ignore - } - } - } - - private final int port; - - public Debugger(int port) { - this.port = port; - } - - private DebugServerThread server = null; - - public synchronized void start() { - if (server == null) { - server = new DebugServerThread(port); - server.start(); - } - } - - public synchronized boolean isRunning() { - return server != null; - } - - public int getPort() { - return port; - } - - public synchronized void stop() { - if (server != null) { - try { - server.ss.close(); - } catch (IOException ex) { - //ignore - } - for (DebugHandler h : server.handlers.values()) { - h.cancel(); - } - server.handlers.clear(); - server = null; - } - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.debugger; + +import com.jpexs.helpers.utf8.Utf8Helper; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * + * @author JPEXS + */ +public class Debugger { + + private static final Set listeners = new HashSet<>(); + + public synchronized void addMessageListener(DebugListener l) { + listeners.add(l); + } + + public synchronized void removeMessageListener(DebugListener l) { + listeners.remove(l); + } + + private static class DebugHandler extends Thread { + + private final Socket s; + + private final int serverPort; + + private static int maxid = 0; + + private final int id; + + public boolean finished = false; + + private final Map parameters = new HashMap<>(); + + public static final int MSG_STRING = 0; + + public static final int MSG_LOADER_URL = 1; + + public static final int MSG_LOADER_BYTES = 2; + + public String getParameter(String name, String defValue) { + if (parameters.containsKey(name)) { + return parameters.get(name); + } + return defValue; + } + + public int getVersionMajor() { + return Integer.parseInt(getParameter("debug.version.major", "1")); + } + + public int getVersionMinor() { + return Integer.parseInt(getParameter("debug.version.major", "0")); + } + + public boolean hasMsgType() { + return getVersionMajor() > 1 || getVersionMinor() > 0; + } + + public DebugHandler(int serverPort, Socket s) { + this.s = s; + id = maxid++; + this.serverPort = serverPort; + } + + public void cancel() { + try { + s.close(); + } catch (IOException ex) { + //ignore + } + } + + private int readType(InputStream is) throws IOException { + int type = is.read(); + if (type == -1) { + throw new EOFException(); + } + return type; + } + + private byte[] readBytes(InputStream is) throws IOException { + int len = is.read(); + if (len == -1) { + throw new EOFException(); + } + int len2 = is.read(); + if (len2 == -1) { + throw new EOFException(); + } + int len3 = is.read(); + if (len3 == -1) { + throw new EOFException(); + } + int len4 = is.read(); + if (len4 == -1) { + throw new EOFException(); + } + len = (len << 24) + (len2 << 16) + (len3 << 8) + len4; + byte[] data = new byte[len]; + int cnt; + int off = 0; + while (len > 0 && (cnt = is.read(data, off, len)) > 0) { + len -= cnt; + off += cnt; + } + return data; + } + + private String readString(InputStream is) throws IOException { + int len = is.read(); + if (len == -1) { + throw new EOFException(); + } + int len2 = is.read(); + if (len2 == -1) { + throw new EOFException(); + } + len = (len << 8) + len2; + + byte[] buf = new byte[len]; + for (int i = 0; i < len; i++) { + int rd = is.read(); + if (rd == -1) { + throw new EOFException(); + } + buf[i] = (byte) rd; + } + return new String(buf, Utf8Helper.charset); + } + + @Override + public void run() { + String clientName = Integer.toString(id); + try (InputStream is = s.getInputStream()) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + int c; + do { + c = is.read(); + if (c != 0) { + baos.write(c); + } + + } while (c > 0); + String ret = baos.toString("UTF-8"); + + if (ret.equals("")) { + try (OutputStream os = s.getOutputStream()) { + os.write(("").getBytes("UTF-8")); + } + } else { + if (!ret.isEmpty()) { + String[] param = (ret.contains(";") ? ret.split(";") : new String[]{ret}); + for (String p : param) { + if (p.contains("=")) { + String key = p.substring(0, p.indexOf('=')); + String val = p.substring(p.indexOf('=') + 1); + parameters.put(key, val); + } else { + parameters.put(p, "true"); + } + } + } + boolean hasType = hasMsgType(); + String name = readString(is); + if (!name.isEmpty()) { + clientName = name; + } + while (true) { + int type = 0; + if (hasType) { + type = readType(is); + } + switch (type) { + case MSG_STRING: + ret = readString(is); + for (DebugListener l : listeners) { + l.onMessage(clientName, ret); + } + break; + case MSG_LOADER_URL: + ret = readString(is); + for (DebugListener l : listeners) { + l.onLoaderURL(clientName, ret); + } + break; + case MSG_LOADER_BYTES: + byte[] retB = readBytes(is); + for (DebugListener l : listeners) { + l.onLoaderBytes(clientName, retB); + } + break; + } + } + } + + } catch (IOException ex) { + //ignore + } + try { + s.close(); + } catch (IOException ex) { + //ignore + } + finished = true; + for (DebugListener l : listeners) { + l.onFinish(clientName); + } + } + } + + private static class DebugServerThread extends Thread { + + private final int port; + + private ServerSocket ss; + + private final Map handlers = new WeakHashMap<>(); + + public DebugServerThread(int port) { + this.port = port; + } + + @Override + public void run() { + try { + ss = new ServerSocket(port, 50, InetAddress.getByName("localhost")); + ss.setReuseAddress(true); + while (true) { + Socket s = ss.accept(); + DebugHandler h = new DebugHandler(port, s); + handlers.put(h.id, h); + h.start(); + } + } catch (IOException ex) { + //ignore + } + } + } + + private final int port; + + public Debugger(int port) { + this.port = port; + } + + private DebugServerThread server = null; + + public synchronized void start() { + if (server == null) { + server = new DebugServerThread(port); + server.start(); + } + } + + public synchronized boolean isRunning() { + return server != null; + } + + public int getPort() { + return port; + } + + public synchronized void stop() { + if (server != null) { + try { + server.ss.close(); + } catch (IOException ex) { + //ignore + } + for (DebugHandler h : server.handlers.values()) { + h.cancel(); + } + server.handlers.clear(); + server = null; + } + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/debugger/DebuggerTools.java b/src/com/jpexs/decompiler/flash/gui/debugger/DebuggerTools.java index dadf683a8..7d38de9a4 100644 --- a/src/com/jpexs/decompiler/flash/gui/debugger/DebuggerTools.java +++ b/src/com/jpexs/decompiler/flash/gui/debugger/DebuggerTools.java @@ -1,247 +1,247 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.debugger; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.abc.ABC; -import com.jpexs.decompiler.flash.abc.ScriptPack; -import com.jpexs.decompiler.flash.abc.types.Multiname; -import com.jpexs.decompiler.flash.abc.types.Namespace; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.gui.DebugLogDialog; -import com.jpexs.decompiler.flash.gui.Main; -import com.jpexs.decompiler.flash.tags.ABCContainerTag; -import com.jpexs.decompiler.flash.tags.FileAttributesTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.helpers.Helper; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Pattern; - -/** - * - * @author JPEXS - */ -public class DebuggerTools { - - private static final Logger logger = Logger.getLogger(DebuggerTools.class.getName()); - - public static final String DEBUGGER_PACKAGE = "com.jpexs.decompiler.flash.debugger"; - - private static volatile Debugger debugger; - - private static ScriptPack getDebuggerScriptPack(SWF swf) { - List allAbcList = new ArrayList<>(); - for (ABCContainerTag ac : swf.getAbcList()) { - allAbcList.add(ac.getABC()); - } - for (ABCContainerTag ac : swf.getAbcList()) { - ABC a = ac.getABC(); - for (ScriptPack m : a.getScriptPacks(DEBUGGER_PACKAGE, allAbcList)) { - if (isDebuggerClass(m.getClassPath().packageStr.toRawString(), null)) { - return m; - } - } - } - return null; - } - - public static boolean hasDebugger(SWF swf) { - return getDebuggerScriptPack(swf) != null; - } - - private static boolean isDebuggerClass(String tested, String cls) { - if (tested == null) { - return false; - } - - // fast check, because dynamic regex compile and match is expensive - if (!tested.startsWith(DEBUGGER_PACKAGE)) { - return false; - } - - if (cls == null) { - cls = ""; - } else { - cls = "\\." + Pattern.quote(cls); - } - - return tested.matches(Pattern.quote(DEBUGGER_PACKAGE) + "(\\.pkg[a-f0-9]+)?" + cls); - } - - public static void injectDebugLoader(SWF swf) { - if (hasDebugger(swf)) { - ScriptPack dsp = getDebuggerScriptPack(swf); - String debuggerPkg = dsp.getClassPath().packageStr.toRawString(); - for (ABCContainerTag ct : swf.getAbcList()) { - ABC a = ct.getABC(); - if (dsp.abc == a) { //do not replace Loader in debugger itself - continue; - } - for (int i = 1; i < a.constants.getMultinameCount(); i++) { - Multiname m = a.constants.getMultiname(i); - if ("flash.display.Loader".equals(m.getNameWithNamespace(a.constants).toRawString())) { - m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); - m.name_index = a.constants.getStringId("DebugLoader", true); - ((Tag) ct).setModified(true); - } else if ("flash.utils.getDefinitionByName".equals(m.getNameWithNamespace(a.constants).toRawString())) { - m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); - m.name_index = a.constants.getStringId("debugGetDefinitionByName", true); - ((Tag) ct).setModified(true); - } else if ("flash.utils.getQualifiedClassName".equals(m.getNameWithNamespace(a.constants).toRawString())) { - m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); - m.name_index = a.constants.getStringId("debugGetQualifiedClassName", true); - ((Tag) ct).setModified(true); - } else if ("flash.utils.getQualifiedSuperclassName".equals(m.getNameWithNamespace(a.constants).toRawString())) { - m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); - m.name_index = a.constants.getStringId("debugGetQualifiedSuperclassName", true); - ((Tag) ct).setModified(true); - } else if ("flash.utils.describeType".equals(m.getNameWithNamespace(a.constants).toRawString())) { - m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); - m.name_index = a.constants.getStringId("debugDescribeType", true); - ((Tag) ct).setModified(true); - } - } - } - } - } - - public static void replaceTraceCalls(SWF swf, String fname) { - if (hasDebugger(swf)) { - String debuggerPkg = getDebuggerScriptPack(swf).getClassPath().packageStr.toRawString(); - //change trace to fname - for (ABCContainerTag ct : swf.getAbcList()) { - ABC a = ct.getABC(); - for (int i = 1; i < a.constants.getMultinameCount(); i++) { - Multiname m = a.constants.getMultiname(i); - if ("trace".equals(m.getNameWithNamespace(a.constants).toRawString())) { - m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); - m.name_index = a.constants.getStringId(fname, true); - ((Tag) ct).setModified(true); - } - } - } - } - } - - public static void switchDebugger(SWF swf) { - int port = Configuration.debuggerPort.get(); - ScriptPack found = getDebuggerScriptPack(swf); - if (found != null) { - ABCContainerTag tag = found.abc.parentTag; - swf.removeTag((Tag) tag); - swf.getAbcList().remove(tag); - - //Change all debugger calls to normal trace / Loader - for (ABCContainerTag ct : swf.getAbcList()) { - ABC a = ct.getABC(); - for (int i = 1; i < a.constants.getMultinameCount(); i++) { - Multiname m = a.constants.getMultiname(i); - String packageStr = m.getNameWithNamespace(a.constants).toString(); - if (isDebuggerClass(packageStr, "debugTrace") - || isDebuggerClass(packageStr, "debugAlert") - || isDebuggerClass(packageStr, "debugSocket") - || isDebuggerClass(packageStr, "debugConsole")) { - m.name_index = a.constants.getStringId("trace", true); - m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, "", 0, true); - ((Tag) ct).setModified(true); - } else if (isDebuggerClass(packageStr, "DebugLoader")) { - m.name_index = a.constants.getStringId("Loader", true); - m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, "flash.display", 0, true); - } - } - } - } else { - Random rnd = new Random(); - byte rb[] = new byte[16]; - rnd.nextBytes(rb); - String rhex = Helper.byteArrayToHex(rb); - try { - //load debug swf - SWF debugSWF = new SWF(Main.class.getClassLoader().getResourceAsStream("com/jpexs/decompiler/flash/gui/debugger/debug.swf"), false); - - List al = swf.getAbcList(); - ABCContainerTag firstAbc = al.isEmpty() ? null : al.get(0); - if (firstAbc == null) { //nothing to instrument? - return; - } - String newdebuggerpkg = DEBUGGER_PACKAGE; - - if (Configuration.randomDebuggerPackage.get()) { - newdebuggerpkg += ".pkg" + rhex; - } - - //add debug ABC tags to main SWF - for (ABCContainerTag ds : debugSWF.getAbcList()) { - ABC a = ds.getABC(); - //Append random hex to Debugger package name - for (int i = 1; i < a.constants.getNamespaceCount(); i++) { - if (a.constants.getNamespace(i).hasName(DEBUGGER_PACKAGE, a.constants)) { - a.constants.getNamespace(i).name_index = a.constants.getStringId(newdebuggerpkg, true); - } - } - //Set debugger port to actually set port - for (int i = 0; i < a.constants.getIntCount(); i++) { - if (a.constants.getInt(i) == 123456L) { - a.constants.setInt(i, (long) port); - } - } - //Add to target SWF - ((Tag) ds).setSwf(swf); - swf.addTag((Tag) ds, (Tag) firstAbc); - swf.getAbcList().add(swf.getAbcList().indexOf(firstAbc), ds); - ((Tag) ds).setModified(true); - - //To allow socket connection to FFDec. Is this safe? - FileAttributesTag ft = swf.getFileAttributes(); - ft.useNetwork = true; - ft.setModified(true); - } - - } catch (Exception ex) { - logger.log(Level.SEVERE, "Error while attaching debugger", ex); - //ignore - } - - } - initDebugger(); - } - - public static Debugger initDebugger() { - if (debugger == null) { - synchronized (Main.class) { - if (debugger == null) { - Debugger dbg = new Debugger(Configuration.debuggerPort.get()); - dbg.start(); - debugger = dbg; - } - } - } - return debugger; - } - - public static void debuggerShowLog() { - initDebugger(); - if (Main.debugDialog == null) { - Main.debugDialog = new DebugLogDialog(debugger); - } - Main.debugDialog.setVisible(true); - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.debugger; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.ScriptPack; +import com.jpexs.decompiler.flash.abc.types.Multiname; +import com.jpexs.decompiler.flash.abc.types.Namespace; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.gui.DebugLogDialog; +import com.jpexs.decompiler.flash.gui.Main; +import com.jpexs.decompiler.flash.tags.ABCContainerTag; +import com.jpexs.decompiler.flash.tags.FileAttributesTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.helpers.Helper; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * + * @author JPEXS + */ +public class DebuggerTools { + + private static final Logger logger = Logger.getLogger(DebuggerTools.class.getName()); + + public static final String DEBUGGER_PACKAGE = "com.jpexs.decompiler.flash.debugger"; + + private static volatile Debugger debugger; + + private static ScriptPack getDebuggerScriptPack(SWF swf) { + List allAbcList = new ArrayList<>(); + for (ABCContainerTag ac : swf.getAbcList()) { + allAbcList.add(ac.getABC()); + } + for (ABCContainerTag ac : swf.getAbcList()) { + ABC a = ac.getABC(); + for (ScriptPack m : a.getScriptPacks(DEBUGGER_PACKAGE, allAbcList)) { + if (isDebuggerClass(m.getClassPath().packageStr.toRawString(), null)) { + return m; + } + } + } + return null; + } + + public static boolean hasDebugger(SWF swf) { + return getDebuggerScriptPack(swf) != null; + } + + private static boolean isDebuggerClass(String tested, String cls) { + if (tested == null) { + return false; + } + + // fast check, because dynamic regex compile and match is expensive + if (!tested.startsWith(DEBUGGER_PACKAGE)) { + return false; + } + + if (cls == null) { + cls = ""; + } else { + cls = "\\." + Pattern.quote(cls); + } + + return tested.matches(Pattern.quote(DEBUGGER_PACKAGE) + "(\\.pkg[a-f0-9]+)?" + cls); + } + + public static void injectDebugLoader(SWF swf) { + if (hasDebugger(swf)) { + ScriptPack dsp = getDebuggerScriptPack(swf); + String debuggerPkg = dsp.getClassPath().packageStr.toRawString(); + for (ABCContainerTag ct : swf.getAbcList()) { + ABC a = ct.getABC(); + if (dsp.abc == a) { //do not replace Loader in debugger itself + continue; + } + for (int i = 1; i < a.constants.getMultinameCount(); i++) { + Multiname m = a.constants.getMultiname(i); + if ("flash.display.Loader".equals(m.getNameWithNamespace(a.constants).toRawString())) { + m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); + m.name_index = a.constants.getStringId("DebugLoader", true); + ((Tag) ct).setModified(true); + } else if ("flash.utils.getDefinitionByName".equals(m.getNameWithNamespace(a.constants).toRawString())) { + m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); + m.name_index = a.constants.getStringId("debugGetDefinitionByName", true); + ((Tag) ct).setModified(true); + } else if ("flash.utils.getQualifiedClassName".equals(m.getNameWithNamespace(a.constants).toRawString())) { + m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); + m.name_index = a.constants.getStringId("debugGetQualifiedClassName", true); + ((Tag) ct).setModified(true); + } else if ("flash.utils.getQualifiedSuperclassName".equals(m.getNameWithNamespace(a.constants).toRawString())) { + m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); + m.name_index = a.constants.getStringId("debugGetQualifiedSuperclassName", true); + ((Tag) ct).setModified(true); + } else if ("flash.utils.describeType".equals(m.getNameWithNamespace(a.constants).toRawString())) { + m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); + m.name_index = a.constants.getStringId("debugDescribeType", true); + ((Tag) ct).setModified(true); + } + } + } + } + } + + public static void replaceTraceCalls(SWF swf, String fname) { + if (hasDebugger(swf)) { + String debuggerPkg = getDebuggerScriptPack(swf).getClassPath().packageStr.toRawString(); + //change trace to fname + for (ABCContainerTag ct : swf.getAbcList()) { + ABC a = ct.getABC(); + for (int i = 1; i < a.constants.getMultinameCount(); i++) { + Multiname m = a.constants.getMultiname(i); + if ("trace".equals(m.getNameWithNamespace(a.constants).toRawString())) { + m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, debuggerPkg, 0, true); + m.name_index = a.constants.getStringId(fname, true); + ((Tag) ct).setModified(true); + } + } + } + } + } + + public static void switchDebugger(SWF swf) { + int port = Configuration.debuggerPort.get(); + ScriptPack found = getDebuggerScriptPack(swf); + if (found != null) { + ABCContainerTag tag = found.abc.parentTag; + swf.removeTag((Tag) tag); + swf.getAbcList().remove(tag); + + //Change all debugger calls to normal trace / Loader + for (ABCContainerTag ct : swf.getAbcList()) { + ABC a = ct.getABC(); + for (int i = 1; i < a.constants.getMultinameCount(); i++) { + Multiname m = a.constants.getMultiname(i); + String packageStr = m.getNameWithNamespace(a.constants).toString(); + if (isDebuggerClass(packageStr, "debugTrace") + || isDebuggerClass(packageStr, "debugAlert") + || isDebuggerClass(packageStr, "debugSocket") + || isDebuggerClass(packageStr, "debugConsole")) { + m.name_index = a.constants.getStringId("trace", true); + m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, "", 0, true); + ((Tag) ct).setModified(true); + } else if (isDebuggerClass(packageStr, "DebugLoader")) { + m.name_index = a.constants.getStringId("Loader", true); + m.namespace_index = a.constants.getNamespaceId(Namespace.KIND_PACKAGE, "flash.display", 0, true); + } + } + } + } else { + Random rnd = new Random(); + byte[] rb = new byte[16]; + rnd.nextBytes(rb); + String rhex = Helper.byteArrayToHex(rb); + try { + //load debug swf + SWF debugSWF = new SWF(Main.class.getClassLoader().getResourceAsStream("com/jpexs/decompiler/flash/gui/debugger/debug.swf"), false); + + List al = swf.getAbcList(); + ABCContainerTag firstAbc = al.isEmpty() ? null : al.get(0); + if (firstAbc == null) { //nothing to instrument? + return; + } + String newdebuggerpkg = DEBUGGER_PACKAGE; + + if (Configuration.randomDebuggerPackage.get()) { + newdebuggerpkg += ".pkg" + rhex; + } + + //add debug ABC tags to main SWF + for (ABCContainerTag ds : debugSWF.getAbcList()) { + ABC a = ds.getABC(); + //Append random hex to Debugger package name + for (int i = 1; i < a.constants.getNamespaceCount(); i++) { + if (a.constants.getNamespace(i).hasName(DEBUGGER_PACKAGE, a.constants)) { + a.constants.getNamespace(i).name_index = a.constants.getStringId(newdebuggerpkg, true); + } + } + //Set debugger port to actually set port + for (int i = 0; i < a.constants.getIntCount(); i++) { + if (a.constants.getInt(i) == 123456L) { + a.constants.setInt(i, (long) port); + } + } + //Add to target SWF + ((Tag) ds).setSwf(swf); + swf.addTag((Tag) ds, (Tag) firstAbc); + swf.getAbcList().add(swf.getAbcList().indexOf(firstAbc), ds); + ((Tag) ds).setModified(true); + + //To allow socket connection to FFDec. Is this safe? + FileAttributesTag ft = swf.getFileAttributes(); + ft.useNetwork = true; + ft.setModified(true); + } + + } catch (Exception ex) { + logger.log(Level.SEVERE, "Error while attaching debugger", ex); + //ignore + } + + } + initDebugger(); + } + + public static Debugger initDebugger() { + if (debugger == null) { + synchronized (Main.class) { + if (debugger == null) { + Debugger dbg = new Debugger(Configuration.debuggerPort.get()); + dbg.start(); + debugger = dbg; + } + } + } + return debugger; + } + + public static void debuggerShowLog() { + initDebugger(); + if (Main.debugDialog == null) { + Main.debugDialog = new DebugLogDialog(debugger); + } + Main.debugDialog.setVisible(true); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java b/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java index 18322c0ac..8c3f484f3 100644 --- a/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java +++ b/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java @@ -1,458 +1,541 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.dumpview; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.abc.ABC; -import com.jpexs.decompiler.flash.abc.ABCInputStream; -import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; -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.DumpInfoSwfNode; -import com.jpexs.decompiler.flash.gui.Main; -import com.jpexs.decompiler.flash.gui.MainPanel; -import com.jpexs.decompiler.flash.gui.TreeNodeType; -import com.jpexs.decompiler.flash.gui.View; -import com.jpexs.decompiler.flash.gui.tagtree.TagTree; -import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; -import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag; -import com.jpexs.decompiler.flash.tags.DefineBitsJPEG3Tag; -import com.jpexs.decompiler.flash.tags.DefineBitsJPEG4Tag; -import com.jpexs.decompiler.flash.tags.DefineBitsLossless2Tag; -import com.jpexs.decompiler.flash.tags.DefineBitsLosslessTag; -import com.jpexs.decompiler.flash.tags.DefineBitsTag; -import com.jpexs.decompiler.flash.tags.DefineButton2Tag; -import com.jpexs.decompiler.flash.tags.DefineButtonTag; -import com.jpexs.decompiler.flash.tags.DefineEditTextTag; -import com.jpexs.decompiler.flash.tags.DefineFont2Tag; -import com.jpexs.decompiler.flash.tags.DefineFont3Tag; -import com.jpexs.decompiler.flash.tags.DefineFont4Tag; -import com.jpexs.decompiler.flash.tags.DefineFontTag; -import com.jpexs.decompiler.flash.tags.DefineMorphShape2Tag; -import com.jpexs.decompiler.flash.tags.DefineMorphShapeTag; -import com.jpexs.decompiler.flash.tags.DefineShape2Tag; -import com.jpexs.decompiler.flash.tags.DefineShape3Tag; -import com.jpexs.decompiler.flash.tags.DefineShape4Tag; -import com.jpexs.decompiler.flash.tags.DefineShapeTag; -import com.jpexs.decompiler.flash.tags.DefineSoundTag; -import com.jpexs.decompiler.flash.tags.DefineSpriteTag; -import com.jpexs.decompiler.flash.tags.DefineText2Tag; -import com.jpexs.decompiler.flash.tags.DefineTextTag; -import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; -import com.jpexs.decompiler.flash.tags.DoABC2Tag; -import com.jpexs.decompiler.flash.tags.DoABCTag; -import com.jpexs.decompiler.flash.tags.DoActionTag; -import com.jpexs.decompiler.flash.tags.DoInitActionTag; -import com.jpexs.decompiler.flash.tags.FileAttributesTag; -import com.jpexs.decompiler.flash.tags.MetadataTag; -import com.jpexs.decompiler.flash.tags.PlaceObject2Tag; -import com.jpexs.decompiler.flash.tags.PlaceObject3Tag; -import com.jpexs.decompiler.flash.tags.PlaceObject4Tag; -import com.jpexs.decompiler.flash.tags.PlaceObjectTag; -import com.jpexs.decompiler.flash.tags.RemoveObject2Tag; -import com.jpexs.decompiler.flash.tags.RemoveObjectTag; -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.gfx.DefineCompactedFont; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.MemoryInputStream; -import java.awt.Color; -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JMenuItem; -import javax.swing.JPopupMenu; -import javax.swing.JTree; -import javax.swing.SwingUtilities; -import javax.swing.plaf.basic.BasicLabelUI; -import javax.swing.plaf.basic.BasicTreeUI; -import javax.swing.tree.DefaultTreeCellRenderer; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreePath; - -/** - * - * @author JPEXS - */ -public class DumpTree extends JTree { - - private static final Logger logger = Logger.getLogger(DumpTree.class.getName()); - - private final MainPanel mainPanel; - - public class DumpTreeCellRenderer extends DefaultTreeCellRenderer { - - public DumpTreeCellRenderer() { - setUI(new BasicLabelUI()); - setOpaque(false); - setBackgroundNonSelectionColor(Color.white); - } - - @Override - public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { - Component ret = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); - if (ret instanceof JLabel) { - JLabel lab = (JLabel) ret; - if (value instanceof DumpInfo) { - DumpInfo di = (DumpInfo) value; - TreeNodeType nodeType = null; - if ("".equals(di.type)) { - nodeType = TreeNodeType.FLASH; - } else if ("TAG".equals(di.type)) { - String name = di.name; - if (name.contains(" ")) { - name = name.substring(0, name.indexOf(' ')).trim(); - } - switch (name) { - case DefineFontTag.NAME: - case DefineFont2Tag.NAME: - case DefineFont3Tag.NAME: - case DefineFont4Tag.NAME: - case DefineCompactedFont.NAME: - nodeType = TreeNodeType.FONT; - break; - case DefineTextTag.NAME: - case DefineText2Tag.NAME: - case DefineEditTextTag.NAME: - nodeType = TreeNodeType.TEXT; - break; - case DefineBitsTag.NAME: - case DefineBitsJPEG2Tag.NAME: - case DefineBitsJPEG3Tag.NAME: - case DefineBitsJPEG4Tag.NAME: - case DefineBitsLosslessTag.NAME: - case DefineBitsLossless2Tag.NAME: - nodeType = TreeNodeType.IMAGE; - break; - case DefineShapeTag.NAME: - case DefineShape2Tag.NAME: - case DefineShape3Tag.NAME: - case DefineShape4Tag.NAME: - nodeType = TreeNodeType.SHAPE; - break; - case DefineMorphShapeTag.NAME: - case DefineMorphShape2Tag.NAME: - nodeType = TreeNodeType.MORPH_SHAPE; - break; - case DefineSpriteTag.NAME: - nodeType = TreeNodeType.SPRITE; - break; - case DefineButtonTag.NAME: - case DefineButton2Tag.NAME: - nodeType = TreeNodeType.BUTTON; - break; - case DefineVideoStreamTag.NAME: - nodeType = TreeNodeType.MOVIE; - break; - - case DefineSoundTag.NAME: - case SoundStreamHeadTag.NAME: - case SoundStreamHead2Tag.NAME: - nodeType = TreeNodeType.SOUND; - break; - case DefineBinaryDataTag.NAME: - nodeType = TreeNodeType.BINARY_DATA; - break; - case DoActionTag.NAME: - case DoInitActionTag.NAME: - case DoABCTag.NAME: - case DoABC2Tag.NAME: - nodeType = TreeNodeType.AS; - break; - case ShowFrameTag.NAME: - nodeType = TreeNodeType.FRAME; //show_frame? - break; - case SetBackgroundColorTag.NAME: - nodeType = TreeNodeType.SET_BACKGROUNDCOLOR; - break; - case FileAttributesTag.NAME: - nodeType = TreeNodeType.FILE_ATTRIBUTES; - break; - case MetadataTag.NAME: - nodeType = TreeNodeType.METADATA; - break; - case PlaceObjectTag.NAME: - case PlaceObject2Tag.NAME: - case PlaceObject3Tag.NAME: - case PlaceObject4Tag.NAME: - nodeType = TreeNodeType.PLACE_OBJECT; - break; - case RemoveObjectTag.NAME: - case RemoveObject2Tag.NAME: - nodeType = TreeNodeType.REMOVE_OBJECT; - break; - default: - nodeType = TreeNodeType.OTHER_TAG; - } - } - if (nodeType != null) { - lab.setIcon(TagTree.getIconForType(nodeType)); - } - } - } - return ret; - - } - } - - public DumpTree(DumpTreeModel treeModel, MainPanel mainPanel) { - super(treeModel); - this.mainPanel = mainPanel; - setCellRenderer(new DumpTreeCellRenderer()); - setRootVisible(false); - setBackground(Color.white); - setUI(new BasicTreeUI() { - { - setHashColor(Color.gray); - } - }); - } - - public void createContextMenu() { - final JPopupMenu contextPopupMenu = new JPopupMenu(); - - final JMenuItem expandRecursiveMenuItem = new JMenuItem(mainPanel.translate("contextmenu.expandAll")); - expandRecursiveMenuItem.addActionListener(this::expandRecursiveButtonActionPerformed); - contextPopupMenu.add(expandRecursiveMenuItem); - - final JMenuItem saveToFileMenuItem = new JMenuItem(mainPanel.translate("contextmenu.saveToFile")); - saveToFileMenuItem.addActionListener(this::saveToFileButtonActionPerformed); - contextPopupMenu.add(saveToFileMenuItem); - - final JMenuItem saveUncompressedToFileMenuItem = new JMenuItem(mainPanel.translate("contextmenu.saveUncompressedToFile")); - saveUncompressedToFileMenuItem.addActionListener(this::saveUncompressedToFileButtonActionPerformed); - contextPopupMenu.add(saveUncompressedToFileMenuItem); - - final JMenuItem closeSelectionMenuItem = new JMenuItem(mainPanel.translate("contextmenu.closeSwf")); - closeSelectionMenuItem.addActionListener(this::closeSwfButtonActionPerformed); - contextPopupMenu.add(closeSelectionMenuItem); - - final JMenuItem parseActionsMenuItem = new JMenuItem(mainPanel.translate("contextmenu.parseActions")); - parseActionsMenuItem.addActionListener(this::parseActionsButtonActionPerformed); - contextPopupMenu.add(parseActionsMenuItem); - - final JMenuItem parseAbcMenuItem = new JMenuItem(mainPanel.translate("contextmenu.parseABC")); - parseAbcMenuItem.addActionListener(this::parseAbcButtonActionPerformed); - contextPopupMenu.add(parseAbcMenuItem); - - final JMenuItem parseInstructionsMenuItem = new JMenuItem(mainPanel.translate("contextmenu.parseInstructions")); - parseInstructionsMenuItem.addActionListener(this::parseInstructionsButtonActionPerformed); - contextPopupMenu.add(parseInstructionsMenuItem); - - addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - - int row = getClosestRowForLocation(e.getX(), e.getY()); - int[] selectionRows = getSelectionRows(); - if (!Helper.contains(selectionRows, row)) { - setSelectionRow(row); - } - - TreePath[] paths = getSelectionPaths(); - if (paths == null || paths.length == 0) { - return; - } - - closeSelectionMenuItem.setVisible(false); - expandRecursiveMenuItem.setVisible(false); - saveToFileMenuItem.setVisible(false); - saveUncompressedToFileMenuItem.setVisible(false); - parseActionsMenuItem.setVisible(false); - parseAbcMenuItem.setVisible(false); - parseInstructionsMenuItem.setVisible(false); - - if (paths.length == 1) { - DumpInfo treeNode = (DumpInfo) paths[0].getLastPathComponent(); - - if (treeNode instanceof DumpInfoSwfNode) { - closeSelectionMenuItem.setVisible(true); - } - - if (treeNode.getEndByte() - treeNode.startByte > 3) { - saveToFileMenuItem.setVisible(true); - if (treeNode.name.equals("bitmapAlphaData") || treeNode.name.equals("zlibBitmapData")) { - 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); - } - - 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); - } - - TreeModel model = getModel(); - expandRecursiveMenuItem.setVisible(model.getChildCount(treeNode) > 0); - } - - contextPopupMenu.show(e.getComponent(), e.getX(), e.getY()); - } - } - }); - } - - private void expandRecursiveButtonActionPerformed(ActionEvent evt) { - TreePath path = getSelectionPath(); - if (path == null) { - return; - } - View.expandTreeNodes(this, path, true); - } - - private void saveToFileButtonActionPerformed(ActionEvent evt) { - saveToFileButtonActionPerformed(false); - } - - private void saveUncompressedToFileButtonActionPerformed(ActionEvent evt) { - saveToFileButtonActionPerformed(true); - } - - private void saveToFileButtonActionPerformed(boolean decompress) { - TreePath[] paths = getSelectionPaths(); - DumpInfo dumpInfo = (DumpInfo) paths[0].getLastPathComponent(); - JFileChooser fc = new JFileChooser(); - String selDir = Configuration.lastOpenDir.get(); - fc.setCurrentDirectory(new File(selDir)); - JFrame f = new JFrame(); - View.setWindowIcon(f); - if (fc.showSaveDialog(f) == JFileChooser.APPROVE_OPTION) { - File sf = Helper.fixDialogFile(fc.getSelectedFile()); - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(sf))) { - 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) { - logger.log(Level.SEVERE, null, ex); - } - } - } - - private void parseActionsButtonActionPerformed(ActionEvent evt) { - TreePath[] paths = getSelectionPaths(); - DumpInfo dumpInfo = (DumpInfo) paths[0].getLastPathComponent(); - SWF swf = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf(); - byte[] data = swf.originalUncompressedData; - int prevLength = (int) dumpInfo.startByte; - try { - SWFInputStream rri = new SWFInputStream(swf, data); - if (prevLength != 0) { - rri.seek(prevLength); - } - List actions = ActionListReader.getOriginalActions(rri, prevLength, (int) dumpInfo.getEndByte()); - for (Action action : actions) { - DumpInfo di = new DumpInfo(action.toString(), "Action", null, action.getAddress(), action.getTotalActionLength()); - di.parent = dumpInfo; - rri.dumpInfo = di; - rri.seek(action.getAddress()); - rri.readAction(); - dumpInfo.getChildInfos().add(di); - } - repaint(); - } catch (IOException | InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - - private void parseAbcButtonActionPerformed(ActionEvent evt) { - TreePath[] paths = getSelectionPaths(); - DumpInfo dumpInfo = (DumpInfo) paths[0].getLastPathComponent(); - SWF swf = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf(); - byte[] data = swf.originalUncompressedData; - int prevLength = (int) dumpInfo.startByte; - try { - ABCInputStream ais = new ABCInputStream(new MemoryInputStream(data, 0, prevLength + (int) dumpInfo.lengthBytes)); - ais.seek(prevLength); - ais.dumpInfo = dumpInfo; - new ABC(ais, swf, null); - } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); - } - repaint(); - } - - private void parseInstructionsButtonActionPerformed(ActionEvent evt) { - TreePath[] paths = getSelectionPaths(); - DumpInfo dumpInfo = (DumpInfo) paths[0].getLastPathComponent(); - SWF swf = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf(); - byte[] data = swf.originalUncompressedData; - int prevLength = (int) dumpInfo.startByte; - try { - ABCInputStream ais = new ABCInputStream(new MemoryInputStream(data, 0, prevLength + (int) dumpInfo.lengthBytes)); - ais.seek(prevLength); - ais.dumpInfo = dumpInfo; - new AVM2Code(ais, null /*FIXME! Pass correct body!*/); - } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); - } - repaint(); - } - - private void closeSwfButtonActionPerformed(ActionEvent evt) { - Main.closeFile(mainPanel.getCurrentSwfList()); - } - - @Override - public DumpTreeModel getModel() { - return (DumpTreeModel) super.getModel(); - } - - public void expandRoot() { - DumpTreeModel dtm = getModel(); - DumpInfo root = dtm.getRoot(); - expandPath(new TreePath(new Object[]{root})); - } - - public void expandFirstLevelNodes() { - DumpTreeModel dtm = getModel(); - DumpInfo root = dtm.getRoot(); - int childCount = dtm.getChildCount(root); - expandPath(new TreePath(new Object[]{root})); - for (int i = 0; i < childCount; i++) { - expandPath(new TreePath(new Object[]{root, dtm.getChild(root, i)})); - } - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.dumpview; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.ABCInputStream; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +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; +import com.jpexs.decompiler.flash.gui.TreeNodeType; +import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.gui.tagtree.TagTree; +import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag; +import com.jpexs.decompiler.flash.tags.DefineBitsJPEG3Tag; +import com.jpexs.decompiler.flash.tags.DefineBitsJPEG4Tag; +import com.jpexs.decompiler.flash.tags.DefineBitsLossless2Tag; +import com.jpexs.decompiler.flash.tags.DefineBitsLosslessTag; +import com.jpexs.decompiler.flash.tags.DefineBitsTag; +import com.jpexs.decompiler.flash.tags.DefineButton2Tag; +import com.jpexs.decompiler.flash.tags.DefineButtonTag; +import com.jpexs.decompiler.flash.tags.DefineEditTextTag; +import com.jpexs.decompiler.flash.tags.DefineFont2Tag; +import com.jpexs.decompiler.flash.tags.DefineFont3Tag; +import com.jpexs.decompiler.flash.tags.DefineFont4Tag; +import com.jpexs.decompiler.flash.tags.DefineFontTag; +import com.jpexs.decompiler.flash.tags.DefineMorphShape2Tag; +import com.jpexs.decompiler.flash.tags.DefineMorphShapeTag; +import com.jpexs.decompiler.flash.tags.DefineShape2Tag; +import com.jpexs.decompiler.flash.tags.DefineShape3Tag; +import com.jpexs.decompiler.flash.tags.DefineShape4Tag; +import com.jpexs.decompiler.flash.tags.DefineShapeTag; +import com.jpexs.decompiler.flash.tags.DefineSoundTag; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.DefineText2Tag; +import com.jpexs.decompiler.flash.tags.DefineTextTag; +import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; +import com.jpexs.decompiler.flash.tags.DoABC2Tag; +import com.jpexs.decompiler.flash.tags.DoABCTag; +import com.jpexs.decompiler.flash.tags.DoActionTag; +import com.jpexs.decompiler.flash.tags.DoInitActionTag; +import com.jpexs.decompiler.flash.tags.FileAttributesTag; +import com.jpexs.decompiler.flash.tags.MetadataTag; +import com.jpexs.decompiler.flash.tags.PlaceObject2Tag; +import com.jpexs.decompiler.flash.tags.PlaceObject3Tag; +import com.jpexs.decompiler.flash.tags.PlaceObject4Tag; +import com.jpexs.decompiler.flash.tags.PlaceObjectTag; +import com.jpexs.decompiler.flash.tags.RemoveObject2Tag; +import com.jpexs.decompiler.flash.tags.RemoveObjectTag; +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; +import java.awt.Color; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JTree; +import javax.swing.SwingUtilities; +import javax.swing.plaf.basic.BasicLabelUI; +import javax.swing.plaf.basic.BasicTreeUI; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; + +/** + * + * @author JPEXS + */ +public class DumpTree extends JTree { + + private static final Logger logger = Logger.getLogger(DumpTree.class.getName()); + + private final MainPanel mainPanel; + + public class DumpTreeCellRenderer extends DefaultTreeCellRenderer { + + public DumpTreeCellRenderer() { + setUI(new BasicLabelUI()); + setOpaque(false); + setBackgroundNonSelectionColor(Color.white); + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { + Component ret = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); + if (ret instanceof JLabel) { + JLabel lab = (JLabel) ret; + if (value instanceof DumpInfo) { + DumpInfo di = (DumpInfo) value; + TreeNodeType nodeType = null; + if ("".equals(di.type)) { + nodeType = TreeNodeType.FLASH; + } else if ("TAG".equals(di.type)) { + String name = di.name; + if (name.contains(" ")) { + name = name.substring(0, name.indexOf(' ')).trim(); + } + switch (name) { + case DefineFontTag.NAME: + case DefineFont2Tag.NAME: + case DefineFont3Tag.NAME: + case DefineFont4Tag.NAME: + case DefineCompactedFont.NAME: + nodeType = TreeNodeType.FONT; + break; + case DefineTextTag.NAME: + case DefineText2Tag.NAME: + case DefineEditTextTag.NAME: + nodeType = TreeNodeType.TEXT; + break; + case DefineBitsTag.NAME: + case DefineBitsJPEG2Tag.NAME: + case DefineBitsJPEG3Tag.NAME: + case DefineBitsJPEG4Tag.NAME: + case DefineBitsLosslessTag.NAME: + case DefineBitsLossless2Tag.NAME: + nodeType = TreeNodeType.IMAGE; + break; + case DefineShapeTag.NAME: + case DefineShape2Tag.NAME: + case DefineShape3Tag.NAME: + case DefineShape4Tag.NAME: + nodeType = TreeNodeType.SHAPE; + break; + case DefineMorphShapeTag.NAME: + case DefineMorphShape2Tag.NAME: + nodeType = TreeNodeType.MORPH_SHAPE; + break; + case DefineSpriteTag.NAME: + nodeType = TreeNodeType.SPRITE; + break; + case DefineButtonTag.NAME: + case DefineButton2Tag.NAME: + nodeType = TreeNodeType.BUTTON; + break; + case DefineVideoStreamTag.NAME: + nodeType = TreeNodeType.MOVIE; + break; + + case DefineSoundTag.NAME: + case SoundStreamHeadTag.NAME: + case SoundStreamHead2Tag.NAME: + nodeType = TreeNodeType.SOUND; + break; + case DefineBinaryDataTag.NAME: + nodeType = TreeNodeType.BINARY_DATA; + break; + case DoActionTag.NAME: + case DoInitActionTag.NAME: + case DoABCTag.NAME: + case DoABC2Tag.NAME: + nodeType = TreeNodeType.AS; + break; + case ShowFrameTag.NAME: + nodeType = TreeNodeType.FRAME; //show_frame? + break; + case SetBackgroundColorTag.NAME: + nodeType = TreeNodeType.SET_BACKGROUNDCOLOR; + break; + case FileAttributesTag.NAME: + nodeType = TreeNodeType.FILE_ATTRIBUTES; + break; + case MetadataTag.NAME: + nodeType = TreeNodeType.METADATA; + break; + case PlaceObjectTag.NAME: + case PlaceObject2Tag.NAME: + case PlaceObject3Tag.NAME: + case PlaceObject4Tag.NAME: + nodeType = TreeNodeType.PLACE_OBJECT; + break; + case RemoveObjectTag.NAME: + case RemoveObject2Tag.NAME: + nodeType = TreeNodeType.REMOVE_OBJECT; + break; + default: + nodeType = TreeNodeType.OTHER_TAG; + } + } + if (nodeType != null) { + lab.setIcon(TagTree.getIconForType(nodeType)); + } + } + } + return ret; + + } + } + + public DumpTree(DumpTreeModel treeModel, MainPanel mainPanel) { + super(treeModel); + this.mainPanel = mainPanel; + setCellRenderer(new DumpTreeCellRenderer()); + setRootVisible(false); + setBackground(Color.white); + setUI(new BasicTreeUI() { + { + setHashColor(Color.gray); + } + }); + } + + public void createContextMenu() { + final JPopupMenu contextPopupMenu = new JPopupMenu(); + + final JMenuItem expandRecursiveMenuItem = new JMenuItem(mainPanel.translate("contextmenu.expandAll")); + expandRecursiveMenuItem.addActionListener(this::expandRecursiveButtonActionPerformed); + contextPopupMenu.add(expandRecursiveMenuItem); + + final JMenuItem saveToFileMenuItem = new JMenuItem(mainPanel.translate("contextmenu.saveToFile")); + saveToFileMenuItem.addActionListener(this::saveToFileButtonActionPerformed); + contextPopupMenu.add(saveToFileMenuItem); + + final JMenuItem saveUncompressedToFileMenuItem = new JMenuItem(mainPanel.translate("contextmenu.saveUncompressedToFile")); + saveUncompressedToFileMenuItem.addActionListener(this::saveUncompressedToFileButtonActionPerformed); + contextPopupMenu.add(saveUncompressedToFileMenuItem); + + final JMenuItem closeSelectionMenuItem = new JMenuItem(mainPanel.translate("contextmenu.closeSwf")); + closeSelectionMenuItem.addActionListener(this::closeSwfButtonActionPerformed); + contextPopupMenu.add(closeSelectionMenuItem); + + final JMenuItem parseActionsMenuItem = new JMenuItem(mainPanel.translate("contextmenu.parseActions")); + parseActionsMenuItem.addActionListener(this::parseActionsButtonActionPerformed); + contextPopupMenu.add(parseActionsMenuItem); + + final JMenuItem parseAbcMenuItem = new JMenuItem(mainPanel.translate("contextmenu.parseABC")); + parseAbcMenuItem.addActionListener(this::parseAbcButtonActionPerformed); + contextPopupMenu.add(parseAbcMenuItem); + + final JMenuItem parseInstructionsMenuItem = new JMenuItem(mainPanel.translate("contextmenu.parseInstructions")); + 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) { + if (SwingUtilities.isRightMouseButton(e)) { + + int row = getClosestRowForLocation(e.getX(), e.getY()); + int[] selectionRows = getSelectionRows(); + if (!Helper.contains(selectionRows, row)) { + setSelectionRow(row); + } + + TreePath[] paths = getSelectionPaths(); + if (paths == null || paths.length == 0) { + return; + } + + closeSelectionMenuItem.setVisible(false); + expandRecursiveMenuItem.setVisible(false); + saveToFileMenuItem.setVisible(false); + saveUncompressedToFileMenuItem.setVisible(false); + 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); + } + + if (treeNode.getEndByte() - treeNode.startByte > 3) { + saveToFileMenuItem.setVisible(true); + if (specialType == DumpInfoSpecialType.ZLIB_DATA) { + saveUncompressedToFileMenuItem.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; + } + } + + 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(); + expandRecursiveMenuItem.setVisible(model.getChildCount(treeNode) > 0); + } + + contextPopupMenu.show(e.getComponent(), e.getX(), e.getY()); + } + } + }); + } + + 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) { + return; + } + View.expandTreeNodes(this, path, true); + } + + private void saveToFileButtonActionPerformed(ActionEvent evt) { + saveToFileButtonActionPerformed(false); + } + + private void saveUncompressedToFileButtonActionPerformed(ActionEvent evt) { + saveToFileButtonActionPerformed(true); + } + + private void saveToFileButtonActionPerformed(boolean decompress) { + TreePath[] paths = getSelectionPaths(); + DumpInfo dumpInfo = (DumpInfo) paths[0].getLastPathComponent(); + JFileChooser fc = new JFileChooser(); + String selDir = Configuration.lastOpenDir.get(); + fc.setCurrentDirectory(new File(selDir)); + JFrame f = new JFrame(); + View.setWindowIcon(f); + 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) { + fos.write(SWFInputStream.uncompressByteArray(data, (int) dumpInfo.startByte, (int) (dumpInfo.getEndByte() - dumpInfo.startByte + 1))); + } else { + fos.write(data, (int) dumpInfo.startByte, (int) (dumpInfo.getEndByte() - dumpInfo.startByte + 1)); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + } + + private void parseActionsButtonActionPerformed(ActionEvent evt) { + TreePath[] paths = getSelectionPaths(); + DumpInfo dumpInfo = (DumpInfo) paths[0].getLastPathComponent(); + SWF swf = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf(); + byte[] data = swf.originalUncompressedData; + int prevLength = (int) dumpInfo.startByte; + try { + SWFInputStream rri = new SWFInputStream(swf, data); + if (prevLength != 0) { + rri.seek(prevLength); + } + List actions = ActionListReader.getOriginalActions(rri, prevLength, (int) dumpInfo.getEndByte()); + for (Action action : actions) { + DumpInfo di = new DumpInfo(action.toString(), "Action", null, action.getAddress(), action.getTotalActionLength()); + di.parent = dumpInfo; + rri.dumpInfo = di; + rri.seek(action.getAddress()); + rri.readAction(); + dumpInfo.getChildInfos().add(di); + } + repaint(); + } catch (IOException | InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + + private void parseAbcButtonActionPerformed(ActionEvent evt) { + TreePath[] paths = getSelectionPaths(); + DumpInfo dumpInfo = (DumpInfo) paths[0].getLastPathComponent(); + SWF swf = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf(); + byte[] data = swf.originalUncompressedData; + int prevLength = (int) dumpInfo.startByte; + try { + ABCInputStream ais = new ABCInputStream(new MemoryInputStream(data, 0, prevLength + (int) dumpInfo.lengthBytes)); + ais.seek(prevLength); + ais.dumpInfo = dumpInfo; + new ABC(ais, swf, null); + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + repaint(); + } + + private void parseInstructionsButtonActionPerformed(ActionEvent evt) { + TreePath[] paths = getSelectionPaths(); + DumpInfo dumpInfo = (DumpInfo) paths[0].getLastPathComponent(); + SWF swf = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf(); + byte[] data = swf.originalUncompressedData; + int prevLength = (int) dumpInfo.startByte; + try { + ABCInputStream ais = new ABCInputStream(new MemoryInputStream(data, 0, prevLength + (int) dumpInfo.lengthBytes)); + ais.seek(prevLength); + ais.dumpInfo = dumpInfo; + new AVM2Code(ais, null /*FIXME! Pass correct body!*/); + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + 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()); + } + + @Override + public DumpTreeModel getModel() { + return (DumpTreeModel) super.getModel(); + } + + public void expandRoot() { + DumpTreeModel dtm = getModel(); + DumpInfo root = dtm.getRoot(); + expandPath(new TreePath(new Object[]{root})); + } + + public void expandFirstLevelNodes() { + DumpTreeModel dtm = getModel(); + DumpInfo root = dtm.getRoot(); + int childCount = dtm.getChildCount(root); + expandPath(new TreePath(new Object[]{root})); + for (int i = 0; i < childCount; i++) { + expandPath(new TreePath(new Object[]{root, dtm.getChild(root, i)})); + } + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 677619e94..16321b6a9 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1,737 +1,738 @@ -# Copyright (C) 2010-2016 JPEXS -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -menu.file = File -menu.file.open = Open... -menu.file.save = Save -menu.file.saveas = Save as... -menu.file.export.fla = Export to FLA -menu.file.export.all = Export all parts -menu.file.export.selection = Export selection -menu.file.exit = Exit - -menu.tools = Tools -menu.tools.searchas = Search All ActionScript... -menu.tools.proxy = Proxy -menu.tools.deobfuscation = Deobfuscation -menu.tools.deobfuscation.pcode = P-code deobfuscation... -menu.tools.deobfuscation.globalrename = Globally rename identifier -menu.tools.deobfuscation.renameinvalid = Rename invalid identifiers -menu.tools.gotoDocumentClass = Go to document class - -menu.settings = Settings -menu.settings.autodeobfuscation = Automatic deobfuscation -menu.settings.internalflashviewer = Use own Flash viewer -menu.settings.parallelspeedup = Parallel SpeedUp -menu.settings.disabledecompilation = Disable decompilation (Disassemble only) -menu.settings.addtocontextmenu = Add FFDec to SWF files context menu -menu.settings.language = Change language -menu.settings.cacheOnDisk = Use caching on disk -menu.settings.gotoMainClassOnStartup = Highlight document class on startup - -menu.help = Help -menu.help.checkupdates = Check for updates... -menu.help.helpus = Help us! -menu.help.homepage = Visit homepage -menu.help.about = About... - -contextmenu.remove = Remove - -button.save = Save -button.edit = Edit -button.cancel = Cancel -button.replace = Replace... - -notavailonthisplatform = Preview of this object is not available on this platform (Windows only). - -swfpreview = SWF preview -swfpreview.internal = SWF preview (Internal viewer) - -parameters = Parameters - -rename.enternew = Enter new name: - -rename.finished.identifier = Identifier renamed. -rename.finished.multiname = %count% multiname(s) renamed. - -node.texts = texts -node.images = images -node.movies = movies -node.sounds = sounds -node.binaryData = binaryData -node.fonts = fonts -node.sprites = sprites -node.shapes = shapes -node.morphshapes = morphshapes -node.buttons = buttons -node.frames = frames -node.scripts = scripts - -message.warning = Warning -message.confirm.experimental = Following procedure can damage SWF file which can be then unplayable.\r\nUSE IT ON YOUR OWN RISK. Do you want to continue? -message.confirm.parallel = Parallelism can speed up loading and decompilation but uses more memory. -message.confirm.on = Do you want to turn this ON? -message.confirm.off = Do you want to turn this OFF? -message.confirm = Confirm - -message.confirm.autodeobfuscate = Automatic deobfuscation is a way to decompile obfuscated code.\r\nDeobfuscation leads to slower decompilation and some of the dead code may be eliminated.\r\nIf the code is not obfuscated, it's better to turn autodeobfuscation off. - -message.parallel = Parallelism -message.trait.saved = Trait successfully saved - -message.constant.new.string = String "%value%" is not present in constants table. Do you want to add it? -message.constant.new.string.title = Add String -message.constant.new.integer = Integer value "%value%" is not present in constants table. Do you want to add it? -message.constant.new.integer.title = Add Integer -message.constant.new.unsignedinteger = Unsigned integer value "%value%" is not present in constants table. Do you want to add it? -message.constant.new.unsignedinteger.title = Add Unsigned integer -message.constant.new.double = Double value "%value%" is not present in constants table. Do you want to add it? -message.constant.new.double.title = Add Double - -work.buffering = Buffering -work.waitingfordissasembly = Waiting for disassembly -work.gettinghilights = Getting highlights -work.disassembling = Disassembling -work.exporting = Exporting -work.searching = Searching -work.renaming = Renaming -work.exporting.fla = Exporting FLA -work.renaming.identifiers = Renaming identifiers -work.deobfuscating = Deobfuscating -work.decompiling = Decompiling -work.gettingvariables = Getting variables -work.reading.swf = Reading SWF -work.creatingwindow = Creating window -work.buildingscripttree = Building script tree - -work.deobfuscating.complete = Deobfuscation complete - -message.search.notfound = String "%searchtext%" not found. -message.search.notfound.title = Not found - -message.rename.notfound.multiname = No multiname found under cursor -message.rename.notfound.identifier = No identifier found under cursor -message.rename.notfound.title = Not found -message.rename.renamed = Identifiers renamed: %count% - -filter.images = Images (%extensions%) -filter.fla = %version% Document (*.fla) -filter.xfl = %version% Uncompressed Document (*.xfl) -filter.swf = SWF files (*.swf) - -error = Error -error.image.invalid = Invalid image. - -error.text.invalid = Invalid text: %text% on line %line% -error.file.save = Cannot save file -error.file.write = Cannot write to the file -error.export = Error during export - -export.select.directory = Select directory to export -export.finishedin = Exported in %time% - -update.check.title = Update check -update.check.nonewversion = No new version available. - -message.helpus = Please visit\r\n%url%\r\nfor details. -message.homepage = Visit homepage at: \r\n%url% - -proxy = Proxy -proxy.start = Start proxy -proxy.stop = Stop proxy -proxy.show = Show proxy -exit = Exit - -panel.disassembled = P-code source -panel.decompiled = ActionScript source - -search.info = Search for "%text%": -search.script = Script - -constants = Constants -traits = Traits - -pleasewait = Please wait - -abc.detail.methodtrait = Method/Getter/Setter Trait -abc.detail.unsupported = - -abc.detail.slotconsttrait = Slot/Const Trait -abc.detail.traitname = Name: - -abc.detail.body.params.maxstack = Max stack: -abc.detail.body.params.localregcount = Local registers count: -abc.detail.body.params.minscope = Minimum scope depth: -abc.detail.body.params.maxscope = Maximum scope depth: -abc.detail.body.params.autofill = Auto fill on code save (GLOBAL SETTING) -abc.detail.body.params.autofill.experimental = ...EXPERIMENTAL - -abc.detail.methodinfo.methodindex = Method Index: -abc.detail.methodinfo.parameters = Parameters: -abc.detail.methodinfo.returnvalue = Return value type: - -error.methodinfo.params = MethodInfo Params Error -error.methodinfo.returnvalue = MethodInfo Return value type Error - -abc.detail.methodinfo = MethodInfo -abc.detail.body.code = MethodBody Code -abc.detail.body.params = MethodBody params - -abc.detail.slotconst.typevalue = Type and Value: - -error.slotconst.typevalue = SlotConst type value Error - -message.autofill.failed = Cannot get code stats for automatic body params.\r\nUncheck autofill to avoid this message. -info.selecttrait = Select class and click a trait in Actionscript source to edit it. - -button.viewgraph = View Graph -button.viewhex = View Hex - -abc.traitslist.instanceinitializer = instance initializer -abc.traitslist.classinitializer = class initializer - -action.edit.experimental = (Experimental) - -message.action.saved = Code successfully saved - -error.action.save = %error% on line %line% - -message.confirm.remove = Are you sure you want to remove %item%\n and all objects which depend on it? - -#after version 1.6.5u1: - -button.ok = OK -button.cancel = Cancel - -font.name = Font name: -font.isbold = Is bold: -font.isitalic = Is italic: -font.ascent = Ascent: -font.descent = Descent: -font.leading = Leading: -font.characters = Characters: -font.characters.add = Add characters: -value.unknown = ? - -yes = yes -no = no - -errors.present = There are ERRORS in the log. Click to view. -errors.none = There are no errors in the log. - -#after version 1.6.6: - -dialog.message.title = Message -dialog.select.title = Select an Option - -button.yes = Yes -button.no = No - -FileChooser.openButtonText = Open -FileChooser.openButtonToolTipText = Open -FileChooser.lookInLabelText = Look in: -FileChooser.acceptAllFileFilterText = All Files -FileChooser.filesOfTypeLabelText = Files of type: -FileChooser.fileNameLabelText = File name: -FileChooser.listViewButtonToolTipText = List -FileChooser.listViewButtonAccessibleName = List -FileChooser.detailsViewButtonToolTipText = Details -FileChooser.detailsViewButtonAccessibleName = Details -FileChooser.upFolderToolTipText = Up One Level -FileChooser.upFolderAccessibleName = Up One Level -FileChooser.homeFolderToolTipText = Home -FileChooser.homeFolderAccessibleName = Home -FileChooser.fileNameHeaderText = Name -FileChooser.fileSizeHeaderText = Size -FileChooser.fileTypeHeaderText = Type -FileChooser.fileDateHeaderText = Date -FileChooser.fileAttrHeaderText = Attributes -FileChooser.openDialogTitleText = Open -FileChooser.directoryDescriptionText = Directory -FileChooser.directoryOpenButtonText = Open -FileChooser.directoryOpenButtonToolTipText = Open selected directory -FileChooser.fileDescriptionText = Generic File -FileChooser.helpButtonText = Help -FileChooser.helpButtonToolTipText = FileChooser help -FileChooser.newFolderAccessibleName = New Folder -FileChooser.newFolderErrorText = Error creating new folder -FileChooser.newFolderToolTipText = Create New Folder -FileChooser.other.newFolder = NewFolder -FileChooser.other.newFolder.subsequent = NewFolder.{0} -FileChooser.win32.newFolder = New Folder -FileChooser.win32.newFolder.subsequent = New Folder ({0}) -FileChooser.saveButtonText = Save -FileChooser.saveButtonToolTipText = Save selected file -FileChooser.saveDialogTitleText = Save -FileChooser.saveInLabelText = Save in: -FileChooser.updateButtonText = Update -FileChooser.updateButtonToolTipText = Update directory listing - -#after version 1.6.6u2: - -FileChooser.detailsViewActionLabel.textAndMnemonic = Details -FileChooser.detailsViewButtonToolTip.textAndMnemonic = Details -FileChooser.fileAttrHeader.textAndMnemonic = Attributes -FileChooser.fileDateHeader.textAndMnemonic = Modified -FileChooser.fileNameHeader.textAndMnemonic = Name -FileChooser.fileNameLabel.textAndMnemonic = File name: -FileChooser.fileSizeHeader.textAndMnemonic = Size -FileChooser.fileTypeHeader.textAndMnemonic = Type -FileChooser.filesOfTypeLabel.textAndMnemonic = Files of type: -FileChooser.folderNameLabel.textAndMnemonic = Folder name: -FileChooser.homeFolderToolTip.textAndMnemonic = Home -FileChooser.listViewActionLabel.textAndMnemonic = List -FileChooser.listViewButtonToolTip.textAndMnemonic = List -FileChooser.lookInLabel.textAndMnemonic = Look in: -FileChooser.newFolderActionLabel.textAndMnemonic = New Folder -FileChooser.newFolderToolTip.textAndMnemonic = Create New Folder -FileChooser.refreshActionLabel.textAndMnemonic = Refresh -FileChooser.saveInLabel.textAndMnemonic = Save in: -FileChooser.upFolderToolTip.textAndMnemonic = Up One Level -FileChooser.viewMenuButtonAccessibleName = View Menu -FileChooser.viewMenuButtonToolTipText = View Menu -FileChooser.viewMenuLabel.textAndMnemonic = View -FileChooser.newFolderActionLabelText = New Folder -FileChooser.listViewActionLabelText = List -FileChooser.detailsViewActionLabelText = Details -FileChooser.refreshActionLabelText = Refresh -FileChooser.sortMenuLabelText = Arrange Icons By -FileChooser.viewMenuLabelText = View -FileChooser.fileSizeKiloBytes = {0} KB -FileChooser.fileSizeMegaBytes = {0} MB -FileChooser.fileSizeGigaBytes = {0} GB -FileChooser.folderNameLabelText = Folder name: - -error.occured = Error occurred: %error% -button.abort = Abort -button.retry = Retry -button.ignore = Ignore - -font.source = Source Font: - -#after version 1.6.7: - -menu.export = Export -menu.general = General -menu.language = Language - -startup.welcometo = Welcome to -startup.selectopen = Click Open icon on the top panel or drag SWF file to this window to start. - -error.font.nocharacter = Selected source font does not contain character "%char%". - -warning.initializers = Static fields and consts are often initialized in initializers.\nEditing value here is usually not enough! - -#after version 1.7.0u1: - -menu.tools.searchMemory = Search SWFs in memory -menu.file.reload = Reload -message.confirm.reload = This action cancels all unsaved changes and reloads the SWF file again.\nDo you want to continue? - -dialog.selectbkcolor.title = Select background color for SWF display -button.selectbkcolor.hint = Select background color - -ColorChooser.okText = OK -ColorChooser.cancelText = Cancel -ColorChooser.resetText = Reset -ColorChooser.previewText = Preview -ColorChooser.swatchesNameText = Swatches -ColorChooser.swatchesRecentText = Recent: -ColorChooser.sampleText = Sample Text Sample Text - -#after version 1.7.1: - -preview.play = Play -preview.pause = Pause -preview.stop = Stop - -message.confirm.removemultiple = Are you sure you want to remove %count% items\n and all objects which depend on it? - -menu.tools.searchCache = Search browsers cache - -#after version 1.7.2u2 - -error.trait.exists = Trait with name "%name%" already exists. -button.addtrait = Add trait -button.font.embed = Embed... -button.yes.all = Yes to all -button.no.all = No to all -message.font.add.exists = Character %char% already exists in the font tag.\nDo you want to replace it? - -filter.gfx = ScaleForm GFx files (*.gfx) -filter.supported = All supported filetypes -work.canceled = Canceled -work.restoringControlFlow = Restoring control flow -menu.advancedsettings.advancedsettings = Advanced Settings -menu.recentFiles = Recent files - -#after version 1.7.4 -work.restoringControlFlow.complete = Control flow restored -message.confirm.recentFileNotFound = File not found. Do you want to remove it from the recent file list? -contextmenu.closeSwf = Close SWF -menu.settings.autoRenameIdentifiers = Auto rename identifiers -menu.file.saveasexe = Save as Exe... -filter.exe = Executable files (*.exe) - -#after version 1.8.0 -font.updateTexts = Update texts - -#after version 1.8.0u1 -menu.file.close = Close -menu.file.closeAll = Close all -menu.tools.otherTools = Other -menu.tools.otherTools.clearRecentFiles = Clear recent files -fontName.name = Font display name: -fontName.copyright = Font copyright: -button.preview = Preview -button.reset = Reset -errors.info = There are INFORMATIONS in the log. Click to view. -errors.warning = There are WARNINGS in the log. Click to view. - -decompilationError = Decompilation error - -disassemblingProgress.toString = toString -disassemblingProgress.reading = Reading -disassemblingProgress.deobfuscating = Deobfuscating - -contextmenu.moveTag = Move tag to - -filter.swc = SWC component files (*.swc) -filter.zip = ZIP compressed files (*.zip) -filter.binary = Binary search - all files (*.*) - -open.error = Error -open.error.fileNotFound = File not found -open.error.cannotOpen = Cannot open file - -node.others = others - -#after version 1.8.1 -menu.tools.search = Text Search - -#after version 1.8.1u1 -menu.tools.timeline = Timeline - -dialog.selectcolor.title = Select color -button.selectcolor.hint = Click to select color - -#default item name, will be used in following sentences -generictag.array.item = item -generictag.array.insertbeginning = Insert %item% at the beginning -generictag.array.insertbefore = Insert %item% before -generictag.array.remove = Remove %item% -generictag.array.insertafter = Insert %item% after -generictag.array.insertend = Insert %item% at the end - -#after version 2.0.0 -contextmenu.expandAll = Expand all - -filter.sounds = Supported sound formats (*.wav, *.mp3) -filter.sounds.wav = Wave file format (*.wav) -filter.sounds.mp3 = MP3 compressed format (*.mp3) - -error.sound.invalid = Invalid sound. - -button.prev = Previous -button.next = Next - -#after version 2.1.0 -message.action.playerglobal.title = PlayerGlobal library needed -message.action.playerglobal.needed = For ActionScript 3 direct editation, a library called "PlayerGlobal.swc" needs to be downloaded from Adobe homepage.\r\n%adobehomepage%\r\nPress OK to go to the download page. -message.action.playerglobal.place = Download the library called PlayerGlobal(.swc), and place it to directory\r\n%libpath%\r\n Press OK to continue. - -message.confirm.experimental.function = This function is EXPERIMENTAL. It means that you should not trust the results and the SWF file can be disfunctional after saving. -message.confirm.donotshowagain = Do not show again - -menu.import = Import -menu.file.import.text = Import text -import.select.directory = Select directory to import -error.text.import = Error during text import. Do you want to continue? - -#after version 2.1.1 -contextmenu.removeWithDependencies = Remove with dependencies - -abc.action.find-usages = Find usages -abc.action.find-declaration = Find declaration - -contextmenu.rawEdit = Raw edit -contextmenu.jumpToCharacter = Jump to character - -menu.settings.dumpView = Dump view - -menu.view = View -menu.file.view.resources = Resources -menu.file.view.hex = Hex dump - -node.header = header - -header.signature = Signature: -header.compression = Compression: -header.compression.lzma = LZMA -header.compression.zlib = ZLIB -header.compression.none = No compression -header.version = SWF Version: -header.gfx = GFX: -header.filesize = File size: -header.framerate = Frame rate: -header.framecount = Frame count: -header.displayrect = Display rect: -header.displayrect.value.twips = %xmin%,%ymin% => %xmax%,%ymax% twips -header.displayrect.value.pixels = %xmin%,%ymin% => %xmax%,%ymax% pixels - -#after version 2.1.2 -contextmenu.saveToFile = Save to File -contextmenu.parseActions = Parse actions -contextmenu.parseABC = Parse ABC -contextmenu.parseInstructions = Parse AVM2 Instructions - -#after version 2.1.3 -menu.deobfuscation = Deobfuscation -menu.file.deobfuscation.old = Old style -menu.file.deobfuscation.new = New style - -#after version 2.1.4 -contextmenu.openswfinside = Open SWF inside -binarydata.swfInside = It looks like there is SWF inside this binary data tag. Click here to load it as subtree. - -#after version 3.0.0 -button.zoomin.hint = Zoom in -button.zoomout.hint = Zoom out -button.zoomfit.hint = Zoom to fit -button.zoomnone.hint = Zoom to 1:1 -button.snapshot.hint = Take snapshot into clipboard - -editorTruncateWarning = Text truncated at position %chars% in debug mode. - -#Font name which is presented in the SWF Font tag -font.name.intag = Font name in tag: - -menu.debugger = Debugger -menu.debugger.switch = Debugger -menu.debugger.replacetrace = Replace trace calls -menu.debugger.showlog = Show Log - -message.debugger = This SWF Debugger can only be used to print messages to log window, browser console or alerts.\r\nIt is NOT designed for features like step code, breakpoints etc. - -contextmenu.addTag = Add tag - -deobfuscation.comment.tryenable = Tip: You can try enabling "Automatic deobfuscation" in Settings -deobfuscation.comment.failed = Deobfuscation is activated but decompilation still failed. If the file is NOT obfuscated, disable "Automatic deobfuscation" for better results. - -#after version 4.0.2 -preview.nextframe = Next frame -preview.prevframe = Previous frame -preview.gotoframe = Goto frame... - -preview.gotoframe.dialog.title = Goto frame -preview.gotoframe.dialog.message = Enter frame number (%min% - %max%) -preview.gotoframe.dialog.frame.error = Invalid frame number. It must be number between %min% and %max%. - -error.text.invalid.continue = Invalid text: %text% on line %line%. Do you want to continue? - -#after version 4.0.5 -contextmenu.copyTag = Copy tag to -fit = fit -button.setAdvanceValues = Set advance values - -menu.tools.replace = Text Replace - -message.confirm.close = There are unsaved changes. Do you really want to close {swfName}? -message.confirm.closeAll = There are unsaved changes. Do you really want to close all SWFs? - -contextmenu.exportJavaSource = Export Java Source -contextmenu.exportSwfXml = Export SWF as XML -contextmenu.importSwfXml = Import SWF XML - -filter.xml = XML - -#after version 4.1.0 -contextmenu.undo = Undo - -text.align.left = Left align -text.align.right = Right align -text.align.center = Center align -text.align.justify = Justify align - -text.undo = Undo changes - -menu.file.import.xml = Import SWF XML -menu.file.export.xml = Export SWF XML - -#after version 4.1.1 -text.align.translatex.decrease = Decrease TranslateX -text.align.translatex.increase = Increase TranslateX -selectPreviousTag = Select previous tag -selectNextTag = Select next tag -button.ignoreAll = Ignore All -menu.file.import.symbolClass = Import Symbol-Class -text.toggleCase = Toggle case - -#after version 5.0.2 -preview.loop = Loop -menu.file.import.script = Import script -contextmenu.copyTagWithDependencies = Copy tag with dependencies to -button.replaceWithTag = Replace with other character tag -button.resolveConstants = Resolve constants - -#after version 5.1.0 -button.viewConstants = View Constants -work.exported = Exported -button.replaceAlphaChannel = Replace alpha channel... - -tagInfo.header.name = Name -tagInfo.header.value = Value -tagInfo.tagType = Tag Type -tagInfo.characterId = Character Id -tagInfo.offset = Offset -tagInfo.length = Length -tagInfo.bounds = Bounds -tagInfo.width = Width -tagInfo.height = Height -tagInfo.neededCharacters = Needed Characters - -button.viewhexpcode = View Hex with instructions -taginfo.header = Basic tag info - -tagInfo.dependentCharacters = Dependent Characters - -#after version 5.3.0 -header.uncompressed = Uncompressed -header.warning.unsupportedGfxCompression = GFX supports only uncompressed or Zlib compressed content. -header.warning.minimumZlibVersion = Zlib compression needs SWF version 6 or greater. -header.warning.minimumLzmaVersion = LZMA compression needs SWF version 13 or greater. - -tagInfo.codecName = Codec Name -tagInfo.exportFormat = Export Format -tagInfo.samplingRate = Sampling Rate -tagInfo.stereo = Stereo -tagInfo.sampleCount = Sample Count - -filter.dmg = Mac Executable files (*.dmg) -filter.linuxExe = Linux Executable files - -import.script.result = %count% scripts imported. -import.script.as12warning = Import script can import only AS1/2 scripts. - -error.constantPoolTooBig = Constant pool is too big. index=%index%, size=%size% -error.image.alpha.invalid = Invalid alpha channel data. - -#after version 6.0.2 -contextmenu.saveUncompressedToFile = Save to Uncompressed File -abc.traitslist.scriptinitializer = script initializer -menu.settings.autoOpenLoadedSWFs = Open loaded SWFs while playing - -#after version 6.1.1 -menu.file.start = Start -menu.file.start.run = Run -menu.file.start.stop = Stop -menu.file.start.debug = Debug -menu.debugging = Debugging -menu.debugging.debug = Debug -menu.debugging.debug.stop = Stop -menu.debugging.debug.pause = Pause -menu.debugging.debug.stepOver = Step over -menu.debugging.debug.stepInto = Step into -menu.debugging.debug.stepOut = Step out -menu.debugging.debug.continue = Continue -menu.debugging.debug.stack = Stack... -menu.debugging.debug.watch = New watch... - -message.playerpath.notset = Flash Player projector not found. Please configure its path in Advanced Settings / Paths (1). -message.playerpath.debug.notset = Flash Player projector content debugger not found. Please configure its path in Advanced Settings / Paths (2). -message.playerpath.lib.notset = PlayerGlobal (.SWC) not found. Please configure its path in Advanced Settings / Paths (3). - -debugpanel.header = Debugging - -variables.header.registers = Registers -variables.header.locals = Locals -variables.header.arguments = Arguments -variables.header.scopeChain = Scope chain -variables.column.name = Name -variables.column.type = Type -variables.column.value = Value - -callStack.header = Call stack -callStack.header.file = File -callStack.header.line = Line - -stack.header = Stack -stack.header.item = Item - -constantpool.header = Constant pool -constantpool.header.id = Id -constantpool.header.value = Value - -work.running = Running -work.debugging = Debugging -work.debugging.instrumenting = Preparing SWF for debugging -work.breakat = Break at\u0020 -work.halted = Debugging started, execution halted. Add breakpoints and click Continue (F5) to resume running. - -debuglog.header = Log -debuglog.button.clear = Clear - -#after 7.0.1 -work.debugging.wait = Waiting for Flash debug projector to connect - -error.debug.listen = Cannot listen on port %port%. There might be other flash debugger running. - -debug.break.reason.unknown = (Unknown) -debug.break.reason.breakpoint = (Breakpoint) -debug.break.reason.watch = (Watch) -debug.break.reason.fault = (Fault) -debug.break.reason.stopRequest = (Stop request) -debug.break.reason.step = (Step) -debug.break.reason.halt = (Halt) -debug.break.reason.scriptLoaded = (Script loaded) - -menu.file.start.debugpcode = Debug P-code - -#after 7.1.2 -button.replaceNoFill = Replace - Update bounds... -message.warning.svgImportExperimental = Not all SVG features are supported. Please check the log after import. - -message.imported.swf = The SWF file uses assets from an imported SWF file:\n%url%\nDo you want the assets to be loaded from that URL? -message.imported.swf.manually = Cannot load imported SWF\n%url%\nThe file or URL does not exist.\nDo you want to select local file? - -message.warning.hexViewNotUpToDate = Hex View is not up-to-date. Please save and reload the file to update Hex View. -message.font.replace.updateTexts = Some characters were replaced. Do you want to update the existing texts? - -menu.settings.simplifyExpressions = Simplify expressions - -#after 8.0.1 -menu.recentFiles.empty = Recent file list is empty -message.warning.outOfMemory32BitJre = OutOfMemory error occurred. You are running 32bit Java on 64bit system. Please use 64bit Java. - -menu.file.reloadAll = Reload all -message.confirm.reloadAll = This action cancels all unsaved changes in all SWF files and reloads whole application again.\nDo you want to continue? -export.script.singleFilePallelModeWarning = Single file script export is not supported with enabled parallel speedup - -button.showOriginalBytesInPcodeHex = Show original bytes -button.remove = Remove -button.showFileOffsetInPcodeHex = Show file offset - -generic.editor.amf3.title = AMF3 editor -generic.editor.amf3.help = AMF3 value syntax:\n\ - ------------------\n\ - scalar types:\n\ - %scalar_samples%\ - other types:\n\ - %nonscalar_samples%\ - \n\ - Notes:\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 +# Copyright (C) 2010-2016 JPEXS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +menu.file = File +menu.file.open = Open... +menu.file.save = Save +menu.file.saveas = Save as... +menu.file.export.fla = Export to FLA +menu.file.export.all = Export all parts +menu.file.export.selection = Export selection +menu.file.exit = Exit + +menu.tools = Tools +menu.tools.searchas = Search All ActionScript... +menu.tools.proxy = Proxy +menu.tools.deobfuscation = Deobfuscation +menu.tools.deobfuscation.pcode = P-code deobfuscation... +menu.tools.deobfuscation.globalrename = Globally rename identifier +menu.tools.deobfuscation.renameinvalid = Rename invalid identifiers +menu.tools.gotoDocumentClass = Go to document class + +menu.settings = Settings +menu.settings.autodeobfuscation = Automatic deobfuscation +menu.settings.internalflashviewer = Use own Flash viewer +menu.settings.parallelspeedup = Parallel SpeedUp +menu.settings.disabledecompilation = Disable decompilation (Disassemble only) +menu.settings.addtocontextmenu = Add FFDec to SWF files context menu +menu.settings.language = Change language +menu.settings.cacheOnDisk = Use caching on disk +menu.settings.gotoMainClassOnStartup = Highlight document class on startup + +menu.help = Help +menu.help.checkupdates = Check for updates... +menu.help.helpus = Help us! +menu.help.homepage = Visit homepage +menu.help.about = About... + +contextmenu.remove = Remove + +button.save = Save +button.edit = Edit +button.cancel = Cancel +button.replace = Replace... + +notavailonthisplatform = Preview of this object is not available on this platform (Windows only). + +swfpreview = SWF preview +swfpreview.internal = SWF preview (Internal viewer) + +parameters = Parameters + +rename.enternew = Enter new name: + +rename.finished.identifier = Identifier renamed. +rename.finished.multiname = %count% multiname(s) renamed. + +node.texts = texts +node.images = images +node.movies = movies +node.sounds = sounds +node.binaryData = binaryData +node.fonts = fonts +node.sprites = sprites +node.shapes = shapes +node.morphshapes = morphshapes +node.buttons = buttons +node.frames = frames +node.scripts = scripts + +message.warning = Warning +message.confirm.experimental = Following procedure can damage SWF file which can be then unplayable.\r\nUSE IT ON YOUR OWN RISK. Do you want to continue? +message.confirm.parallel = Parallelism can speed up loading and decompilation but uses more memory. +message.confirm.on = Do you want to turn this ON? +message.confirm.off = Do you want to turn this OFF? +message.confirm = Confirm + +message.confirm.autodeobfuscate = Automatic deobfuscation is a way to decompile obfuscated code.\r\nDeobfuscation leads to slower decompilation and some of the dead code may be eliminated.\r\nIf the code is not obfuscated, it's better to turn autodeobfuscation off. + +message.parallel = Parallelism +message.trait.saved = Trait successfully saved + +message.constant.new.string = String "%value%" is not present in constants table. Do you want to add it? +message.constant.new.string.title = Add String +message.constant.new.integer = Integer value "%value%" is not present in constants table. Do you want to add it? +message.constant.new.integer.title = Add Integer +message.constant.new.unsignedinteger = Unsigned integer value "%value%" is not present in constants table. Do you want to add it? +message.constant.new.unsignedinteger.title = Add Unsigned integer +message.constant.new.double = Double value "%value%" is not present in constants table. Do you want to add it? +message.constant.new.double.title = Add Double + +work.buffering = Buffering +work.waitingfordissasembly = Waiting for disassembly +work.gettinghilights = Getting highlights +work.disassembling = Disassembling +work.exporting = Exporting +work.searching = Searching +work.renaming = Renaming +work.exporting.fla = Exporting FLA +work.renaming.identifiers = Renaming identifiers +work.deobfuscating = Deobfuscating +work.decompiling = Decompiling +work.gettingvariables = Getting variables +work.reading.swf = Reading SWF +work.creatingwindow = Creating window +work.buildingscripttree = Building script tree + +work.deobfuscating.complete = Deobfuscation complete + +message.search.notfound = String "%searchtext%" not found. +message.search.notfound.title = Not found + +message.rename.notfound.multiname = No multiname found under cursor +message.rename.notfound.identifier = No identifier found under cursor +message.rename.notfound.title = Not found +message.rename.renamed = Identifiers renamed: %count% + +filter.images = Images (%extensions%) +filter.fla = %version% Document (*.fla) +filter.xfl = %version% Uncompressed Document (*.xfl) +filter.swf = SWF files (*.swf) + +error = Error +error.image.invalid = Invalid image. + +error.text.invalid = Invalid text: %text% on line %line% +error.file.save = Cannot save file +error.file.write = Cannot write to the file +error.export = Error during export + +export.select.directory = Select directory to export +export.finishedin = Exported in %time% + +update.check.title = Update check +update.check.nonewversion = No new version available. + +message.helpus = Please visit\r\n%url%\r\nfor details. +message.homepage = Visit homepage at: \r\n%url% + +proxy = Proxy +proxy.start = Start proxy +proxy.stop = Stop proxy +proxy.show = Show proxy +exit = Exit + +panel.disassembled = P-code source +panel.decompiled = ActionScript source + +search.info = Search for "%text%": +search.script = Script + +constants = Constants +traits = Traits + +pleasewait = Please wait + +abc.detail.methodtrait = Method/Getter/Setter Trait +abc.detail.unsupported = - +abc.detail.slotconsttrait = Slot/Const Trait +abc.detail.traitname = Name: + +abc.detail.body.params.maxstack = Max stack: +abc.detail.body.params.localregcount = Local registers count: +abc.detail.body.params.minscope = Minimum scope depth: +abc.detail.body.params.maxscope = Maximum scope depth: +abc.detail.body.params.autofill = Auto fill on code save (GLOBAL SETTING) +abc.detail.body.params.autofill.experimental = ...EXPERIMENTAL + +abc.detail.methodinfo.methodindex = Method Index: +abc.detail.methodinfo.parameters = Parameters: +abc.detail.methodinfo.returnvalue = Return value type: + +error.methodinfo.params = MethodInfo Params Error +error.methodinfo.returnvalue = MethodInfo Return value type Error + +abc.detail.methodinfo = MethodInfo +abc.detail.body.code = MethodBody Code +abc.detail.body.params = MethodBody params + +abc.detail.slotconst.typevalue = Type and Value: + +error.slotconst.typevalue = SlotConst type value Error + +message.autofill.failed = Cannot get code stats for automatic body params.\r\nUncheck autofill to avoid this message. +info.selecttrait = Select class and click a trait in Actionscript source to edit it. + +button.viewgraph = View Graph +button.viewhex = View Hex + +abc.traitslist.instanceinitializer = instance initializer +abc.traitslist.classinitializer = class initializer + +action.edit.experimental = (Experimental) + +message.action.saved = Code successfully saved + +error.action.save = %error% on line %line% + +message.confirm.remove = Are you sure you want to remove %item%\n and all objects which depend on it? + +#after version 1.6.5u1: + +button.ok = OK +button.cancel = Cancel + +font.name = Font name: +font.isbold = Is bold: +font.isitalic = Is italic: +font.ascent = Ascent: +font.descent = Descent: +font.leading = Leading: +font.characters = Characters: +font.characters.add = Add characters: +value.unknown = ? + +yes = yes +no = no + +errors.present = There are ERRORS in the log. Click to view. +errors.none = There are no errors in the log. + +#after version 1.6.6: + +dialog.message.title = Message +dialog.select.title = Select an Option + +button.yes = Yes +button.no = No + +FileChooser.openButtonText = Open +FileChooser.openButtonToolTipText = Open +FileChooser.lookInLabelText = Look in: +FileChooser.acceptAllFileFilterText = All Files +FileChooser.filesOfTypeLabelText = Files of type: +FileChooser.fileNameLabelText = File name: +FileChooser.listViewButtonToolTipText = List +FileChooser.listViewButtonAccessibleName = List +FileChooser.detailsViewButtonToolTipText = Details +FileChooser.detailsViewButtonAccessibleName = Details +FileChooser.upFolderToolTipText = Up One Level +FileChooser.upFolderAccessibleName = Up One Level +FileChooser.homeFolderToolTipText = Home +FileChooser.homeFolderAccessibleName = Home +FileChooser.fileNameHeaderText = Name +FileChooser.fileSizeHeaderText = Size +FileChooser.fileTypeHeaderText = Type +FileChooser.fileDateHeaderText = Date +FileChooser.fileAttrHeaderText = Attributes +FileChooser.openDialogTitleText = Open +FileChooser.directoryDescriptionText = Directory +FileChooser.directoryOpenButtonText = Open +FileChooser.directoryOpenButtonToolTipText = Open selected directory +FileChooser.fileDescriptionText = Generic File +FileChooser.helpButtonText = Help +FileChooser.helpButtonToolTipText = FileChooser help +FileChooser.newFolderAccessibleName = New Folder +FileChooser.newFolderErrorText = Error creating new folder +FileChooser.newFolderToolTipText = Create New Folder +FileChooser.other.newFolder = NewFolder +FileChooser.other.newFolder.subsequent = NewFolder.{0} +FileChooser.win32.newFolder = New Folder +FileChooser.win32.newFolder.subsequent = New Folder ({0}) +FileChooser.saveButtonText = Save +FileChooser.saveButtonToolTipText = Save selected file +FileChooser.saveDialogTitleText = Save +FileChooser.saveInLabelText = Save in: +FileChooser.updateButtonText = Update +FileChooser.updateButtonToolTipText = Update directory listing + +#after version 1.6.6u2: + +FileChooser.detailsViewActionLabel.textAndMnemonic = Details +FileChooser.detailsViewButtonToolTip.textAndMnemonic = Details +FileChooser.fileAttrHeader.textAndMnemonic = Attributes +FileChooser.fileDateHeader.textAndMnemonic = Modified +FileChooser.fileNameHeader.textAndMnemonic = Name +FileChooser.fileNameLabel.textAndMnemonic = File name: +FileChooser.fileSizeHeader.textAndMnemonic = Size +FileChooser.fileTypeHeader.textAndMnemonic = Type +FileChooser.filesOfTypeLabel.textAndMnemonic = Files of type: +FileChooser.folderNameLabel.textAndMnemonic = Folder name: +FileChooser.homeFolderToolTip.textAndMnemonic = Home +FileChooser.listViewActionLabel.textAndMnemonic = List +FileChooser.listViewButtonToolTip.textAndMnemonic = List +FileChooser.lookInLabel.textAndMnemonic = Look in: +FileChooser.newFolderActionLabel.textAndMnemonic = New Folder +FileChooser.newFolderToolTip.textAndMnemonic = Create New Folder +FileChooser.refreshActionLabel.textAndMnemonic = Refresh +FileChooser.saveInLabel.textAndMnemonic = Save in: +FileChooser.upFolderToolTip.textAndMnemonic = Up One Level +FileChooser.viewMenuButtonAccessibleName = View Menu +FileChooser.viewMenuButtonToolTipText = View Menu +FileChooser.viewMenuLabel.textAndMnemonic = View +FileChooser.newFolderActionLabelText = New Folder +FileChooser.listViewActionLabelText = List +FileChooser.detailsViewActionLabelText = Details +FileChooser.refreshActionLabelText = Refresh +FileChooser.sortMenuLabelText = Arrange Icons By +FileChooser.viewMenuLabelText = View +FileChooser.fileSizeKiloBytes = {0} KB +FileChooser.fileSizeMegaBytes = {0} MB +FileChooser.fileSizeGigaBytes = {0} GB +FileChooser.folderNameLabelText = Folder name: + +error.occured = Error occurred: %error% +button.abort = Abort +button.retry = Retry +button.ignore = Ignore + +font.source = Source Font: + +#after version 1.6.7: + +menu.export = Export +menu.general = General +menu.language = Language + +startup.welcometo = Welcome to +startup.selectopen = Click Open icon on the top panel or drag SWF file to this window to start. + +error.font.nocharacter = Selected source font does not contain character "%char%". + +warning.initializers = Static fields and consts are often initialized in initializers.\nEditing value here is usually not enough! + +#after version 1.7.0u1: + +menu.tools.searchMemory = Search SWFs in memory +menu.file.reload = Reload +message.confirm.reload = This action cancels all unsaved changes and reloads the SWF file again.\nDo you want to continue? + +dialog.selectbkcolor.title = Select background color for SWF display +button.selectbkcolor.hint = Select background color + +ColorChooser.okText = OK +ColorChooser.cancelText = Cancel +ColorChooser.resetText = Reset +ColorChooser.previewText = Preview +ColorChooser.swatchesNameText = Swatches +ColorChooser.swatchesRecentText = Recent: +ColorChooser.sampleText = Sample Text Sample Text + +#after version 1.7.1: + +preview.play = Play +preview.pause = Pause +preview.stop = Stop + +message.confirm.removemultiple = Are you sure you want to remove %count% items\n and all objects which depend on it? + +menu.tools.searchCache = Search browsers cache + +#after version 1.7.2u2 + +error.trait.exists = Trait with name "%name%" already exists. +button.addtrait = Add trait +button.font.embed = Embed... +button.yes.all = Yes to all +button.no.all = No to all +message.font.add.exists = Character %char% already exists in the font tag.\nDo you want to replace it? + +filter.gfx = ScaleForm GFx files (*.gfx) +filter.supported = All supported filetypes +work.canceled = Canceled +work.restoringControlFlow = Restoring control flow +menu.advancedsettings.advancedsettings = Advanced Settings +menu.recentFiles = Recent files + +#after version 1.7.4 +work.restoringControlFlow.complete = Control flow restored +message.confirm.recentFileNotFound = File not found. Do you want to remove it from the recent file list? +contextmenu.closeSwf = Close SWF +menu.settings.autoRenameIdentifiers = Auto rename identifiers +menu.file.saveasexe = Save as Exe... +filter.exe = Executable files (*.exe) + +#after version 1.8.0 +font.updateTexts = Update texts + +#after version 1.8.0u1 +menu.file.close = Close +menu.file.closeAll = Close all +menu.tools.otherTools = Other +menu.tools.otherTools.clearRecentFiles = Clear recent files +fontName.name = Font display name: +fontName.copyright = Font copyright: +button.preview = Preview +button.reset = Reset +errors.info = There are INFORMATIONS in the log. Click to view. +errors.warning = There are WARNINGS in the log. Click to view. + +decompilationError = Decompilation error + +disassemblingProgress.toString = toString +disassemblingProgress.reading = Reading +disassemblingProgress.deobfuscating = Deobfuscating + +contextmenu.moveTag = Move tag to + +filter.swc = SWC component files (*.swc) +filter.zip = ZIP compressed files (*.zip) +filter.binary = Binary search - all files (*.*) + +open.error = Error +open.error.fileNotFound = File not found +open.error.cannotOpen = Cannot open file + +node.others = others + +#after version 1.8.1 +menu.tools.search = Text Search + +#after version 1.8.1u1 +menu.tools.timeline = Timeline + +dialog.selectcolor.title = Select color +button.selectcolor.hint = Click to select color + +#default item name, will be used in following sentences +generictag.array.item = item +generictag.array.insertbeginning = Insert %item% at the beginning +generictag.array.insertbefore = Insert %item% before +generictag.array.remove = Remove %item% +generictag.array.insertafter = Insert %item% after +generictag.array.insertend = Insert %item% at the end + +#after version 2.0.0 +contextmenu.expandAll = Expand all + +filter.sounds = Supported sound formats (*.wav, *.mp3) +filter.sounds.wav = Wave file format (*.wav) +filter.sounds.mp3 = MP3 compressed format (*.mp3) + +error.sound.invalid = Invalid sound. + +button.prev = Previous +button.next = Next + +#after version 2.1.0 +message.action.playerglobal.title = PlayerGlobal library needed +message.action.playerglobal.needed = For ActionScript 3 direct editation, a library called "PlayerGlobal.swc" needs to be downloaded from Adobe homepage.\r\n%adobehomepage%\r\nPress OK to go to the download page. +message.action.playerglobal.place = Download the library called PlayerGlobal(.swc), and place it to directory\r\n%libpath%\r\n Press OK to continue. + +message.confirm.experimental.function = This function is EXPERIMENTAL. It means that you should not trust the results and the SWF file can be disfunctional after saving. +message.confirm.donotshowagain = Do not show again + +menu.import = Import +menu.file.import.text = Import text +import.select.directory = Select directory to import +error.text.import = Error during text import. Do you want to continue? + +#after version 2.1.1 +contextmenu.removeWithDependencies = Remove with dependencies + +abc.action.find-usages = Find usages +abc.action.find-declaration = Find declaration + +contextmenu.rawEdit = Raw edit +contextmenu.jumpToCharacter = Jump to character + +menu.settings.dumpView = Dump view + +menu.view = View +menu.file.view.resources = Resources +menu.file.view.hex = Hex dump + +node.header = header + +header.signature = Signature: +header.compression = Compression: +header.compression.lzma = LZMA +header.compression.zlib = ZLIB +header.compression.none = No compression +header.version = SWF Version: +header.gfx = GFX: +header.filesize = File size: +header.framerate = Frame rate: +header.framecount = Frame count: +header.displayrect = Display rect: +header.displayrect.value.twips = %xmin%,%ymin% => %xmax%,%ymax% twips +header.displayrect.value.pixels = %xmin%,%ymin% => %xmax%,%ymax% pixels + +#after version 2.1.2 +contextmenu.saveToFile = Save to File +contextmenu.parseActions = Parse actions +contextmenu.parseABC = Parse ABC +contextmenu.parseInstructions = Parse AVM2 Instructions + +#after version 2.1.3 +menu.deobfuscation = Deobfuscation +menu.file.deobfuscation.old = Old style +menu.file.deobfuscation.new = New style + +#after version 2.1.4 +contextmenu.openswfinside = Open SWF inside +binarydata.swfInside = It looks like there is SWF inside this binary data tag. Click here to load it as subtree. + +#after version 3.0.0 +button.zoomin.hint = Zoom in +button.zoomout.hint = Zoom out +button.zoomfit.hint = Zoom to fit +button.zoomnone.hint = Zoom to 1:1 +button.snapshot.hint = Take snapshot into clipboard + +editorTruncateWarning = Text truncated at position %chars% in debug mode. + +#Font name which is presented in the SWF Font tag +font.name.intag = Font name in tag: + +menu.debugger = Debugger +menu.debugger.switch = Debugger +menu.debugger.replacetrace = Replace trace calls +menu.debugger.showlog = Show Log + +message.debugger = This SWF Debugger can only be used to print messages to log window, browser console or alerts.\r\nIt is NOT designed for features like step code, breakpoints etc. + +contextmenu.addTag = Add tag + +deobfuscation.comment.tryenable = Tip: You can try enabling "Automatic deobfuscation" in Settings +deobfuscation.comment.failed = Deobfuscation is activated but decompilation still failed. If the file is NOT obfuscated, disable "Automatic deobfuscation" for better results. + +#after version 4.0.2 +preview.nextframe = Next frame +preview.prevframe = Previous frame +preview.gotoframe = Goto frame... + +preview.gotoframe.dialog.title = Goto frame +preview.gotoframe.dialog.message = Enter frame number (%min% - %max%) +preview.gotoframe.dialog.frame.error = Invalid frame number. It must be number between %min% and %max%. + +error.text.invalid.continue = Invalid text: %text% on line %line%. Do you want to continue? + +#after version 4.0.5 +contextmenu.copyTag = Copy tag to +fit = fit +button.setAdvanceValues = Set advance values + +menu.tools.replace = Text Replace + +message.confirm.close = There are unsaved changes. Do you really want to close {swfName}? +message.confirm.closeAll = There are unsaved changes. Do you really want to close all SWFs? + +contextmenu.exportJavaSource = Export Java Source +contextmenu.exportSwfXml = Export SWF as XML +contextmenu.importSwfXml = Import SWF XML + +filter.xml = XML + +#after version 4.1.0 +contextmenu.undo = Undo + +text.align.left = Left align +text.align.right = Right align +text.align.center = Center align +text.align.justify = Justify align + +text.undo = Undo changes + +menu.file.import.xml = Import SWF XML +menu.file.export.xml = Export SWF XML + +#after version 4.1.1 +text.align.translatex.decrease = Decrease TranslateX +text.align.translatex.increase = Increase TranslateX +selectPreviousTag = Select previous tag +selectNextTag = Select next tag +button.ignoreAll = Ignore All +menu.file.import.symbolClass = Import Symbol-Class +text.toggleCase = Toggle case + +#after version 5.0.2 +preview.loop = Loop +menu.file.import.script = Import script +contextmenu.copyTagWithDependencies = Copy tag with dependencies to +button.replaceWithTag = Replace with other character tag +button.resolveConstants = Resolve constants + +#after version 5.1.0 +button.viewConstants = View Constants +work.exported = Exported +button.replaceAlphaChannel = Replace alpha channel... + +tagInfo.header.name = Name +tagInfo.header.value = Value +tagInfo.tagType = Tag Type +tagInfo.characterId = Character Id +tagInfo.offset = Offset +tagInfo.length = Length +tagInfo.bounds = Bounds +tagInfo.width = Width +tagInfo.height = Height +tagInfo.neededCharacters = Needed Characters + +button.viewhexpcode = View Hex with instructions +taginfo.header = Basic tag info + +tagInfo.dependentCharacters = Dependent Characters + +#after version 5.3.0 +header.uncompressed = Uncompressed +header.warning.unsupportedGfxCompression = GFX supports only uncompressed or Zlib compressed content. +header.warning.minimumZlibVersion = Zlib compression needs SWF version 6 or greater. +header.warning.minimumLzmaVersion = LZMA compression needs SWF version 13 or greater. + +tagInfo.codecName = Codec Name +tagInfo.exportFormat = Export Format +tagInfo.samplingRate = Sampling Rate +tagInfo.stereo = Stereo +tagInfo.sampleCount = Sample Count + +filter.dmg = Mac Executable files (*.dmg) +filter.linuxExe = Linux Executable files + +import.script.result = %count% scripts imported. +import.script.as12warning = Import script can import only AS1/2 scripts. + +error.constantPoolTooBig = Constant pool is too big. index=%index%, size=%size% +error.image.alpha.invalid = Invalid alpha channel data. + +#after version 6.0.2 +contextmenu.saveUncompressedToFile = Save to Uncompressed File +abc.traitslist.scriptinitializer = script initializer +menu.settings.autoOpenLoadedSWFs = Open loaded SWFs while playing + +#after version 6.1.1 +menu.file.start = Start +menu.file.start.run = Run +menu.file.start.stop = Stop +menu.file.start.debug = Debug +menu.debugging = Debugging +menu.debugging.debug = Debug +menu.debugging.debug.stop = Stop +menu.debugging.debug.pause = Pause +menu.debugging.debug.stepOver = Step over +menu.debugging.debug.stepInto = Step into +menu.debugging.debug.stepOut = Step out +menu.debugging.debug.continue = Continue +menu.debugging.debug.stack = Stack... +menu.debugging.debug.watch = New watch... + +message.playerpath.notset = Flash Player projector not found. Please configure its path in Advanced Settings / Paths (1). +message.playerpath.debug.notset = Flash Player projector content debugger not found. Please configure its path in Advanced Settings / Paths (2). +message.playerpath.lib.notset = PlayerGlobal (.SWC) not found. Please configure its path in Advanced Settings / Paths (3). + +debugpanel.header = Debugging + +variables.header.registers = Registers +variables.header.locals = Locals +variables.header.arguments = Arguments +variables.header.scopeChain = Scope chain +variables.column.name = Name +variables.column.type = Type +variables.column.value = Value + +callStack.header = Call stack +callStack.header.file = File +callStack.header.line = Line + +stack.header = Stack +stack.header.item = Item + +constantpool.header = Constant pool +constantpool.header.id = Id +constantpool.header.value = Value + +work.running = Running +work.debugging = Debugging +work.debugging.instrumenting = Preparing SWF for debugging +work.breakat = Break at\u0020 +work.halted = Debugging started, execution halted. Add breakpoints and click Continue (F5) to resume running. + +debuglog.header = Log +debuglog.button.clear = Clear + +#after 7.0.1 +work.debugging.wait = Waiting for Flash debug projector to connect + +error.debug.listen = Cannot listen on port %port%. There might be other flash debugger running. + +debug.break.reason.unknown = (Unknown) +debug.break.reason.breakpoint = (Breakpoint) +debug.break.reason.watch = (Watch) +debug.break.reason.fault = (Fault) +debug.break.reason.stopRequest = (Stop request) +debug.break.reason.step = (Step) +debug.break.reason.halt = (Halt) +debug.break.reason.scriptLoaded = (Script loaded) + +menu.file.start.debugpcode = Debug P-code + +#after 7.1.2 +button.replaceNoFill = Replace - Update bounds... +message.warning.svgImportExperimental = Not all SVG features are supported. Please check the log after import. + +message.imported.swf = The SWF file uses assets from an imported SWF file:\n%url%\nDo you want the assets to be loaded from that URL? +message.imported.swf.manually = Cannot load imported SWF\n%url%\nThe file or URL does not exist.\nDo you want to select local file? + +message.warning.hexViewNotUpToDate = Hex View is not up-to-date. Please save and reload the file to update Hex View. +message.font.replace.updateTexts = Some characters were replaced. Do you want to update the existing texts? + +menu.settings.simplifyExpressions = Simplify expressions + +#after 8.0.1 +menu.recentFiles.empty = Recent file list is empty +message.warning.outOfMemory32BitJre = OutOfMemory error occurred. You are running 32bit Java on 64bit system. Please use 64bit Java. + +menu.file.reloadAll = Reload all +message.confirm.reloadAll = This action cancels all unsaved changes in all SWF files and reloads whole application again.\nDo you want to continue? +export.script.singleFilePallelModeWarning = Single file script export is not supported with enabled parallel speedup + +button.showOriginalBytesInPcodeHex = Show original bytes +button.remove = Remove +button.showFileOffsetInPcodeHex = Show file offset + +generic.editor.amf3.title = AMF3 editor +generic.editor.amf3.help = AMF3 value syntax:\n\ + ------------------\n\ + scalar types:\n\ + %scalar_samples%\ + other types:\n\ + %nonscalar_samples%\ + \n\ + Notes:\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 +contextmenu.showInResources = Show in Resources \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java b/src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java index 7f195773b..86031bb80 100644 --- a/src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java +++ b/src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java @@ -1,97 +1,97 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.pipes; - -import com.sun.jna.Platform; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.WinNT.HANDLE; -import com.sun.jna.ptr.IntByReference; -import java.io.IOException; -import java.io.InputStream; - -/** - * - * @author JPEXS - */ -public class PipeInputStream extends InputStream { - - protected HANDLE pipe; - - private boolean closed = false; - - public PipeInputStream(String pipeName, boolean newpipe) throws IOException { - if (!Platform.isWindows()) { - throw new IOException("Cannot create Pipe on nonWindows OS"); - } - String fullPipePath = "\\\\.\\pipe\\" + pipeName; - if (newpipe) { - pipe = Kernel32.INSTANCE.CreateNamedPipe(fullPipePath, Kernel32.PIPE_ACCESS_INBOUND, Kernel32.PIPE_TYPE_BYTE, 1, 4096, 4096, 0, null); - if (pipe == null || !Kernel32.INSTANCE.ConnectNamedPipe(pipe, null)) { - throw new IOException("Cannot connect to the pipe"); - } - } else { - pipe = Kernel32.INSTANCE.CreateFile(fullPipePath, Kernel32.GENERIC_READ, Kernel32.FILE_SHARE_READ, null, Kernel32.OPEN_EXISTING, Kernel32.FILE_ATTRIBUTE_NORMAL, null); - } - if (pipe == null) { - throw new IOException("Cannot connect to the pipe"); - } - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - try { - close(); - } catch (IOException ex) { - //ignore - } - } - - }); - } - - @Override - public synchronized void close() throws IOException { - if (!closed) { - Kernel32.INSTANCE.CloseHandle(pipe); - closed = true; - } - } - - @Override - public synchronized int read() throws IOException { - byte d[] = new byte[1]; - if (readPipe(d) == 0) { - return -1; - } - return d[0]; - } - - private int readPipe(byte res[]) throws IOException { - final IntByReference ibr = new IntByReference(); - int read = 0; - while (read < res.length) { - byte[] data = new byte[res.length - read]; - boolean result = Kernel32.INSTANCE.ReadFile(pipe, data, data.length, ibr, null); - if (!result) { - throw new IOException("Cannot read pipe. Error " + Kernel32.INSTANCE.GetLastError()); - } - int readNow = ibr.getValue(); - System.arraycopy(data, 0, res, read, readNow); - read += readNow; - } - return read; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.pipes; + +import com.sun.jna.Platform; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.ptr.IntByReference; +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @author JPEXS + */ +public class PipeInputStream extends InputStream { + + protected HANDLE pipe; + + private boolean closed = false; + + public PipeInputStream(String pipeName, boolean newpipe) throws IOException { + if (!Platform.isWindows()) { + throw new IOException("Cannot create Pipe on nonWindows OS"); + } + String fullPipePath = "\\\\.\\pipe\\" + pipeName; + if (newpipe) { + pipe = Kernel32.INSTANCE.CreateNamedPipe(fullPipePath, Kernel32.PIPE_ACCESS_INBOUND, Kernel32.PIPE_TYPE_BYTE, 1, 4096, 4096, 0, null); + if (pipe == null || !Kernel32.INSTANCE.ConnectNamedPipe(pipe, null)) { + throw new IOException("Cannot connect to the pipe"); + } + } else { + pipe = Kernel32.INSTANCE.CreateFile(fullPipePath, Kernel32.GENERIC_READ, Kernel32.FILE_SHARE_READ, null, Kernel32.OPEN_EXISTING, Kernel32.FILE_ATTRIBUTE_NORMAL, null); + } + if (pipe == null) { + throw new IOException("Cannot connect to the pipe"); + } + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + try { + close(); + } catch (IOException ex) { + //ignore + } + } + + }); + } + + @Override + public synchronized void close() throws IOException { + if (!closed) { + Kernel32.INSTANCE.CloseHandle(pipe); + closed = true; + } + } + + @Override + public synchronized int read() throws IOException { + byte[] d = new byte[1]; + if (readPipe(d) == 0) { + return -1; + } + return d[0]; + } + + private int readPipe(byte res[]) throws IOException { + final IntByReference ibr = new IntByReference(); + int read = 0; + while (read < res.length) { + byte[] data = new byte[res.length - read]; + boolean result = Kernel32.INSTANCE.ReadFile(pipe, data, data.length, ibr, null); + if (!result) { + throw new IOException("Cannot read pipe. Error " + Kernel32.INSTANCE.GetLastError()); + } + int readNow = ibr.getValue(); + System.arraycopy(data, 0, res, read, readNow); + read += readNow; + } + return read; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java b/src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java index 5ca2bd6f2..40c0774c5 100644 --- a/src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java +++ b/src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java @@ -1,85 +1,85 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.pipes; - -import com.sun.jna.Platform; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.WinNT.HANDLE; -import com.sun.jna.ptr.IntByReference; -import java.io.IOException; -import java.io.OutputStream; - -/** - * - * @author JPEXS - */ -public class PipeOutputStream extends OutputStream { - - protected HANDLE pipe; - - private boolean closed = false; - - public PipeOutputStream(String pipeName, boolean newPipe) throws IOException { - if (!Platform.isWindows()) { - throw new IOException("Cannot create Pipe on nonWindows OS"); - } - String fullPipePath = "\\\\.\\pipe\\" + pipeName; - if (newPipe) { - pipe = Kernel32.INSTANCE.CreateNamedPipe(fullPipePath, Kernel32.PIPE_ACCESS_OUTBOUND, Kernel32.PIPE_TYPE_BYTE, 1, 4096, 4096, 0, null); - if (pipe == null || !Kernel32.INSTANCE.ConnectNamedPipe(pipe, null)) { - throw new IOException("Cannot connect to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); - } - } else { - pipe = Kernel32.INSTANCE.CreateFile(fullPipePath, Kernel32.GENERIC_WRITE, Kernel32.FILE_SHARE_WRITE, null, Kernel32.OPEN_EXISTING, Kernel32.FILE_ATTRIBUTE_NORMAL, null); - if (pipe == null) { - throw new IOException("Cannot connect to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); - } - } - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - try { - close(); - } catch (IOException ex) { - //ignore - } - } - - }); - } - - @Override - public synchronized void close() throws IOException { - if (!closed) { - Kernel32.INSTANCE.CloseHandle(pipe); - closed = true; - } - } - - @Override - public synchronized void write(int b) throws IOException { - byte data[] = new byte[]{(byte) b}; - IntByReference ibr = new IntByReference(); - boolean result = Kernel32.INSTANCE.WriteFile(pipe, data, data.length, ibr, null); - if (!result) { - throw new IOException("Cannot write to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); - } - if (ibr.getValue() != data.length) { - throw new IOException("Cannot write to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); - } - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.pipes; + +import com.sun.jna.Platform; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.ptr.IntByReference; +import java.io.IOException; +import java.io.OutputStream; + +/** + * + * @author JPEXS + */ +public class PipeOutputStream extends OutputStream { + + protected HANDLE pipe; + + private boolean closed = false; + + public PipeOutputStream(String pipeName, boolean newPipe) throws IOException { + if (!Platform.isWindows()) { + throw new IOException("Cannot create Pipe on nonWindows OS"); + } + String fullPipePath = "\\\\.\\pipe\\" + pipeName; + if (newPipe) { + pipe = Kernel32.INSTANCE.CreateNamedPipe(fullPipePath, Kernel32.PIPE_ACCESS_OUTBOUND, Kernel32.PIPE_TYPE_BYTE, 1, 4096, 4096, 0, null); + if (pipe == null || !Kernel32.INSTANCE.ConnectNamedPipe(pipe, null)) { + throw new IOException("Cannot connect to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); + } + } else { + pipe = Kernel32.INSTANCE.CreateFile(fullPipePath, Kernel32.GENERIC_WRITE, Kernel32.FILE_SHARE_WRITE, null, Kernel32.OPEN_EXISTING, Kernel32.FILE_ATTRIBUTE_NORMAL, null); + if (pipe == null) { + throw new IOException("Cannot connect to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); + } + } + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + try { + close(); + } catch (IOException ex) { + //ignore + } + } + + }); + } + + @Override + public synchronized void close() throws IOException { + if (!closed) { + Kernel32.INSTANCE.CloseHandle(pipe); + closed = true; + } + } + + @Override + public synchronized void write(int b) throws IOException { + byte[] data = new byte[]{(byte) b}; + IntByReference ibr = new IntByReference(); + boolean result = Kernel32.INSTANCE.WriteFile(pipe, data, data.length, ibr, null); + if (!result) { + throw new IOException("Cannot write to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); + } + if (ibr.getValue() != data.length) { + throw new IOException("Cannot write to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); + } + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/proxy/ProxyFrame.java b/src/com/jpexs/decompiler/flash/gui/proxy/ProxyFrame.java index 97b56d89f..c121d2b6b 100644 --- a/src/com/jpexs/decompiler/flash/gui/proxy/ProxyFrame.java +++ b/src/com/jpexs/decompiler/flash/gui/proxy/ProxyFrame.java @@ -1,759 +1,759 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.proxy; - -import com.jpexs.decompiler.flash.RetryTask; -import com.jpexs.decompiler.flash.RunnableIOEx; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.gui.AppFrame; -import com.jpexs.decompiler.flash.gui.AppStrings; -import com.jpexs.decompiler.flash.gui.GuiAbortRetryIgnoreHandler; -import com.jpexs.decompiler.flash.gui.Main; -import com.jpexs.decompiler.flash.gui.MainFrame; -import com.jpexs.decompiler.flash.gui.View; -import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.utf8.Utf8InputStreamReader; -import com.jpexs.helpers.utf8.Utf8OutputStreamWriter; -import com.jpexs.proxy.CatchedListener; -import com.jpexs.proxy.ReplacedListener; -import com.jpexs.proxy.Replacement; -import com.jpexs.proxy.Server; -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Font; -import java.awt.Image; -import java.awt.Toolkit; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.StringSelection; -import java.awt.event.ActionEvent; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.JTextField; -import javax.swing.SwingConstants; -import javax.swing.filechooser.FileFilter; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.DefaultTableColumnModel; -import javax.swing.table.DefaultTableModel; - -/** - * Frame with Proxy - * - * @author JPEXS - */ -public class ProxyFrame extends AppFrame implements CatchedListener, MouseListener, ReplacedListener { - - private static final String REPLACEMENTS_NAME = "replacements.cfg"; - - private JTable replacementsTable; - - private JButton switchButton = new JButton(translate("proxy.start")); - - private boolean started = false; - - private JTextField portField = new JTextField("55555"); - - private JCheckBox sniffSWFCheckBox = new JCheckBox("SWF", false); - - private JCheckBox sniffOSCheckBox = new JCheckBox("OctetStream", false); - - private JCheckBox sniffJSCheckBox = new JCheckBox("JS", false); - - private JCheckBox sniffXMLCheckBox = new JCheckBox("XML", false); - - /** - * Is server running - * - * @return True when running - */ - public boolean isRunning() { - return started; - } - - /** - * Sets port for the proxy - * - * @param port Port number - */ - public void setPort(int port) { - portField.setText(Integer.toString(port)); - } - - private static class SizeItem implements Comparable { - - String file; - - public SizeItem(String file) { - this.file = file; - } - - @Override - public String toString() { - return Helper.byteCountStr(new File(file).length(), false); - } - - @Override - public int compareTo(SizeItem o) { - return (int) (new File(file).length() - new File(o.file).length()); - } - } - - DefaultTableModel tableModel; - - private SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss"); - - /** - * List of replacements - */ - private static List replacements = new ArrayList<>(); - - /** - * Saves replacements to file for future use - */ - private static void saveReplacements() { - String replacementsFile = getReplacementsFile(); - if (replacements.isEmpty()) { - File rf = new File(replacementsFile); - if (rf.exists()) { - if (!rf.delete()) { - Logger.getLogger(ProxyFrame.class.getName()).log(Level.SEVERE, "Cannot delete replacements file"); - } - } - } else { - try (PrintWriter pw = new PrintWriter(new Utf8OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(replacementsFile))))) { - for (Replacement r : replacements) { - pw.println(r.urlPattern); - pw.println(r.targetFile); - } - } catch (IOException ex) { - Logger.getLogger(ProxyFrame.class.getName()).log(Level.SEVERE, "Exception during saving replacements", ex); - } - } - } - - /** - * Load replacements from file - */ - private static void loadReplacements() { - String replacementsFile = getReplacementsFile(); - if (!(new File(replacementsFile)).exists()) { - return; - } - replacements = new ArrayList<>(); - try (BufferedReader br = new BufferedReader(new Utf8InputStreamReader(new FileInputStream(replacementsFile)))) { - String s; - while ((s = br.readLine()) != null) { - Replacement r = new Replacement(s, br.readLine()); - replacements.add(r); - } - } catch (IOException e) { - //ignore - } - } - - private static String getReplacementsFile() { - return Configuration.getFFDecHome() + REPLACEMENTS_NAME; - } - - /** - * Constructor - * - * @param mainFrame Main frame - */ - public ProxyFrame(final MainFrame mainFrame) { - - final String[] columnNames = new String[]{ - translate("column.accessed"), - translate("column.size"), - translate("column.url")}; - - loadReplacements(); - - Object data[][] = new Object[replacements.size()][3]; - - for (int i = 0; i < replacements.size(); i++) { - Replacement r = replacements.get(i); - data[i][0] = r.lastAccess == null ? "" : format.format(r.lastAccess.getTime()); - data[i][1] = new SizeItem(r.targetFile); - data[i][2] = r.urlPattern; - } - - tableModel = new DefaultTableModel(data, columnNames) { - - @Override - public Class getColumnClass(int columnIndex) { - Class classes[] = new Class[]{String.class, SizeItem.class, String.class}; - return classes[columnIndex]; - } - - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - - }; - replacementsTable = new JTable(tableModel); - - DefaultTableCellRenderer tcr = new DefaultTableCellRenderer(); - tcr.setHorizontalAlignment(SwingConstants.RIGHT); - - replacementsTable.setDefaultRenderer(String.class, new DefaultTableCellRenderer()); - replacementsTable.setDefaultRenderer(SizeItem.class, tcr); - - replacementsTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); - - replacementsTable.setRowSelectionAllowed(true); - - DefaultTableColumnModel colModel = (DefaultTableColumnModel) replacementsTable.getColumnModel(); - colModel.getColumn(0).setMaxWidth(100); - - colModel.getColumn(1).setMaxWidth(200); - - replacementsTable.setAutoCreateRowSorter(true); - - replacementsTable.setAutoCreateRowSorter(false); - - replacementsTable.addMouseListener(this); - replacementsTable.setFont(new Font("Monospaced", Font.PLAIN, 12)); - switchButton.addActionListener(this::switchStateButtonActionPerformed); - Container cnt = getContentPane(); - cnt.setLayout(new BorderLayout()); - cnt.add(new JScrollPane(replacementsTable), BorderLayout.CENTER); - - portField.setPreferredSize(new Dimension(80, portField.getPreferredSize().height)); - JPanel buttonsPanel = new JPanel(); - buttonsPanel.setLayout(new FlowLayout()); - buttonsPanel.add(new JLabel(translate("port"))); - buttonsPanel.add(portField); - buttonsPanel.add(switchButton); - cnt.add(buttonsPanel, BorderLayout.NORTH); - - JPanel buttonsPanel23 = new JPanel(); - buttonsPanel23.setLayout(new BoxLayout(buttonsPanel23, BoxLayout.Y_AXIS)); - - JPanel buttonsPanel21 = new JPanel(new FlowLayout()); - JButton openButton = new JButton(translate("open")); - openButton.addActionListener(this::openButtonActionPerformed); - buttonsPanel21.add(openButton); - JButton clearButton = new JButton(translate("clear")); - clearButton.addActionListener(this::clearButtonActionPerformed); - buttonsPanel21.add(clearButton); - JButton renameButton = new JButton(translate("rename")); - renameButton.addActionListener(this::renameButtonActionPerformed); - buttonsPanel21.add(renameButton); - JButton removeButton = new JButton(translate("remove")); - removeButton.addActionListener(this::removeButtonActionPerformed); - buttonsPanel21.add(removeButton); - - //JPanel buttonsPanel22 = new JPanel(new FlowLayout()); - JButton copyUrlButton = new JButton(translate("copy.url")); - copyUrlButton.addActionListener(this::copyUrlButtonActionPerformed); - buttonsPanel21.add(copyUrlButton); - - JButton saveAsButton = new JButton(translate("save.as")); - saveAsButton.addActionListener(this::saveAsButtonActionPerformed); - buttonsPanel21.add(saveAsButton); - - JButton replaceButton = new JButton(translate("replace")); - replaceButton.addActionListener(this::replaceButtonActionPerformed); - buttonsPanel21.add(replaceButton); - - JPanel buttonsPanel3 = new JPanel(); - buttonsPanel3.setLayout(new FlowLayout()); - buttonsPanel3.add(new JLabel(translate("sniff"))); - buttonsPanel3.add(sniffSWFCheckBox); - buttonsPanel3.add(sniffOSCheckBox); - //buttonsPanel3.add(sniffJSCheckBox); - //buttonsPanel3.add(sniffXMLCheckBox); - - buttonsPanel23.add(buttonsPanel21); - //buttonsPanel23.add(buttonsPanel22); - buttonsPanel23.add(buttonsPanel3); - - cnt.add(buttonsPanel23, BorderLayout.SOUTH); - setSize(800, 500); - View.centerScreen(this); - View.setWindowIcon(this); - setTitle(translate("dialog.title")); - this.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - setVisible(false); - Main.removeTrayIcon(); - if (mainFrame != null) { - if (mainFrame.isVisible()) { - return; - } - } - Main.showModeFrame(); - } - - /** - * Invoked when a window is iconified. - */ - @Override - public void windowIconified(WindowEvent e) { - setVisible(false); - } - }); - List images = new ArrayList<>(); - images.add(View.loadImage("proxy16")); - images.add(View.loadImage("proxy32")); - setIconImages(images); - } - - private void open() { - if (replacementsTable.getSelectedRow() > -1) { - Replacement r = replacements.get(replacementsTable.getRowSorter().convertRowIndexToModel(replacementsTable.getSelectedRow())); - Main.openFile(r.targetFile, r.urlPattern); - } - } - - private String selectExportDir() { - JFileChooser chooser = new JFileChooser(); - chooser.setCurrentDirectory(new File(Configuration.lastExportDir.get())); - chooser.setDialogTitle(translate("export.select.directory")); - chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - chooser.setAcceptAllFileFilterUsed(false); - if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { - final String selFile = Helper.fixDialogFile(chooser.getSelectedFile()).getAbsolutePath(); - Configuration.lastExportDir.set(Helper.fixDialogFile(chooser.getSelectedFile()).getAbsolutePath()); - return selFile; - } - return null; - } - - private int[] getSelectedRows() { - int sel[] = replacementsTable.getSelectedRows(); - for (int i = 0; i < sel.length; i++) { - sel[i] = replacementsTable.getRowSorter().convertRowIndexToModel(sel[i]); - } - - return sel; - } - - private void openButtonActionPerformed(ActionEvent evt) { - open(); - } - - private void saveAsButtonActionPerformed(ActionEvent evt) { - int[] sel = getSelectedRows(); - if (sel.length == 1) { - Replacement r = replacements.get(sel[0]); - JFileChooser fc = new JFileChooser(); - fc.setCurrentDirectory(new File(Configuration.lastSaveDir.get())); - String n = r.urlPattern; - if (n.contains("?")) { - n = n.substring(0, n.indexOf('?')); - } - if (n.contains("/")) { - n = n.substring(n.lastIndexOf('/')); - } - n = Helper.makeFileName(n); - fc.setSelectedFile(new File(Configuration.lastSaveDir.get(), n)); - String ext = ".swf"; - final String extension = ext; - FileFilter swfFilter = new FileFilter() { - @Override - public boolean accept(File f) { - return (f.getName().toLowerCase().endsWith(extension)) || (f.isDirectory()); - } - - @Override - public String getDescription() { - return AppStrings.translate("filter" + extension); - } - }; - fc.setFileFilter(swfFilter); - fc.setAcceptAllFileFilterUsed(true); - JFrame f = new JFrame(); - View.setWindowIcon(f); - if (fc.showSaveDialog(f) == JFileChooser.APPROVE_OPTION) { - File file = Helper.fixDialogFile(fc.getSelectedFile()); - try { - Files.copy(new File(r.targetFile).toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException ex) { - View.showMessageDialog(this, translate("error.save.as") + "\r\n" + ex.getLocalizedMessage(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } - } - } else { - GuiAbortRetryIgnoreHandler handler = new GuiAbortRetryIgnoreHandler(); - File exportDir = new File(selectExportDir()); - for (int s : sel) { - final Replacement r = replacements.get(s); - String n = r.urlPattern; - if (n.contains("?")) { - n = n.substring(0, n.indexOf('?')); - } - if (n.contains("/")) { - n = n.substring(n.lastIndexOf('/')); - } - n = Helper.makeFileName(n); - int c = 2; - String n2 = n; - while (new File(exportDir, n2).exists()) { - if (n.contains(".")) { - n2 = n.substring(0, n.lastIndexOf('.')) + c + n.substring(n.lastIndexOf('.')); - c++; - } else { - n2 = n + c + ".swf"; - c++; - } - } - - final File outfile = new File(exportDir, n2); - try { - new RetryTask(new RunnableIOEx() { - @Override - public void run() throws IOException { - Files.copy(new File(r.targetFile).toPath(), outfile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - }, handler).run(); - } catch (IOException | InterruptedException ex) { - break; - } - } - } - } - - private void replaceButtonActionPerformed(ActionEvent evt) { - int[] sel = getSelectedRows(); - if (sel.length > 0) { - Replacement r = replacements.get(sel[0]); - JFileChooser fc = new JFileChooser(); - fc.setCurrentDirectory(new File(Configuration.lastOpenDir.get())); - String ext = ".swf"; - final String extension = ext; - FileFilter swfFilter = new FileFilter() { - @Override - public boolean accept(File f) { - return (f.getName().toLowerCase().endsWith(extension)) || (f.isDirectory()); - } - - @Override - public String getDescription() { - return AppStrings.translate("filter" + extension); - } - }; - fc.setFileFilter(swfFilter); - fc.setAcceptAllFileFilterUsed(true); - JFrame f = new JFrame(); - View.setWindowIcon(f); - if (fc.showOpenDialog(f) == JFileChooser.APPROVE_OPTION) { - File file = Helper.fixDialogFile(fc.getSelectedFile()); - try { - Files.copy(file.toPath(), new File(r.targetFile).toPath(), StandardCopyOption.REPLACE_EXISTING); - tableModel.fireTableCellUpdated(sel[0], 1/*size*/); - } catch (IOException ex) { - View.showMessageDialog(f, translate("error.replace") + "\r\n" + ex.getLocalizedMessage(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } - } - } - } - - private void copyUrlButtonActionPerformed(ActionEvent evt) { - int[] sel = getSelectedRows(); - StringBuilder copyText = new StringBuilder(); - for (int sc : sel) { - Replacement r = replacements.get(sc); - if (copyText.length() > 0) { - copyText.append(System.lineSeparator()); - } - copyText.append(r.urlPattern); - } - - if (copyText.length() > 0) { - Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - StringSelection stringSelection = new StringSelection(copyText.toString()); - clipboard.setContents(stringSelection, null); - } - } - - private void renameButtonActionPerformed(ActionEvent evt) { - int[] sel = getSelectedRows(); - if (sel.length > 0) { - Replacement r = replacements.get(sel[0]); - String s = View.showInputDialog("URL", r.urlPattern); - if (s != null) { - r.urlPattern = s; - tableModel.setValueAt(s, sel[0], 2/*url*/); - } - } - } - - private void clearButtonActionPerformed(ActionEvent evt) { - for (Replacement r : replacements) { - File f; - try { - f = (new File(Main.tempFile(r.targetFile))); - if (f.exists()) { - f.delete(); - } - } catch (IOException ex) { - Logger.getLogger(ProxyFrame.class.getName()).log(Level.SEVERE, null, ex); - } - } - tableModel.setRowCount(0); - replacements.clear(); - saveReplacements(); - } - - private void removeButtonActionPerformed(ActionEvent evt) { - int[] sel = getSelectedRows(); - Arrays.sort(sel); - for (int i = sel.length - 1; i >= 0; i--) { - tableModel.removeRow(sel[i]); - Replacement r = replacements.remove(sel[i]); - saveReplacements(); - File f = (new File(r.targetFile)); - if (f.exists()) { - f.delete(); - } - } - } - - private void switchStateButtonActionPerformed(ActionEvent evt) { - Main.switchProxy(); - } - - /** - * Switch proxy state - */ - public void switchState() { - started = !started; - if (started) { - int port = 0; - try { - port = Integer.parseInt(portField.getText()); - } catch (NumberFormatException nfe) { - } - if ((port <= 0) || (port > 65535)) { - View.showMessageDialog(this, translate("error.port"), translate("error"), JOptionPane.ERROR_MESSAGE); - started = false; - return; - } - List catchedContentTypes = new ArrayList<>(); - catchedContentTypes.add("application/x-shockwave-flash"); - catchedContentTypes.add("application/x-javascript"); - catchedContentTypes.add("application/javascript"); - catchedContentTypes.add("text/javascript"); - catchedContentTypes.add("application/json"); - catchedContentTypes.add("text/xml"); - catchedContentTypes.add("application/xml"); - catchedContentTypes.add("application/octet-stream"); - if (!Server.startServer(port, replacements, catchedContentTypes, this, this)) { - JOptionPane.showMessageDialog(this, translate("error.start.server").replace("%port%", "" + port), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - started = false; - return; - } - switchButton.setText(translate("proxy.stop")); - portField.setEditable(false); - } else { - Server.stopServer(); - switchButton.setText(translate("proxy.start")); - portField.setEditable(true); - } - } - - /** - * Mouse clicked event - * - * @param e event - */ - @Override - public void mouseClicked(MouseEvent e) { - if (e.getSource() == replacementsTable) { - if (e.getClickCount() == 2) { - open(); - } - } - } - - /** - * Mouse pressed event - * - * @param e event - */ - @Override - public void mousePressed(MouseEvent e) { - } - - /** - * Mouse released event - * - * @param e event - */ - @Override - public void mouseReleased(MouseEvent e) { - } - - /** - * Mouse entered event - * - * @param e event - */ - @Override - public void mouseEntered(MouseEvent e) { - } - - /** - * Mouse exited event - * - * @param e event - */ - @Override - public void mouseExited(MouseEvent e) { - } - - /** - * Method called when specified contentType is received - * - * @param contentType Content type - * @param url URL of the method - * @param data Data stream - * @return replacement data - */ - @Override - public byte[] catched(String contentType, String url, InputStream data) { - boolean swfOnly = false; - if (contentType.contains(";")) { - contentType = contentType.substring(0, contentType.indexOf(';')); - } - if ((!sniffSWFCheckBox.isSelected()) && (contentType.equals("application/x-shockwave-flash"))) { - return null; - } - if ((!sniffJSCheckBox.isSelected()) && (contentType.equals("application/javascript") || contentType.equals("application/x-javascript") || contentType.equals("text/javascript") || contentType.equals("application/json"))) { - return null; - } - if ((!sniffXMLCheckBox.isSelected()) && (contentType.equals("application/xml") || contentType.equals("text/xml"))) { - return null; - } - if ((!sniffOSCheckBox.isSelected()) && (contentType.equals("application/octet-stream"))) { - return null; - } - - byte[] result = null; - - boolean cont = false; - for (Replacement r : replacements) { - if (r.matches(url)) { - cont = true; - break; - } - } - if (!cont) { - try { - byte[] hdr = new byte[3]; - if (data.read(hdr) != 3) { - throw new IOException(); - } - - String shdr = new String(hdr); - if (swfOnly && ((!shdr.equals("FWS")) && (!shdr.equals("CWS")) && (!shdr.equals("ZWS")))) { - return null; //NOT SWF - } - - String tempFilePath = Main.tempFile(url); - data.reset(); - byte[] dataArray = Helper.readStream(data); - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(new File(tempFilePath)))) { - fos.write(dataArray); - } - - result = SWFDecompilerPlugin.fireProxyFileCatched(dataArray); - - Replacement r = new Replacement(url, tempFilePath); - r.lastAccess = Calendar.getInstance(); - replacements.add(r); - saveReplacements(); - tableModel.addRow(new Object[]{ - r.lastAccess == null ? "" : format.format(r.lastAccess.getTime()), - new SizeItem(r.targetFile), - r.urlPattern - }); - } catch (IOException e) { - } - } - - return result; - } - - /** - * Shows or hides this {@code Window} depending on the value of parameter - * {@code b}. - * - * @param b if {@code true}, makes the {@code Window} visible, otherwise - * hides the {@code Window}. If the {@code Window} and/or its owner are not - * yet displayable, both are made displayable. The {@code Window} will be - * validated prior to being made visible. If the {@code Window} is already - * visible, this will bring the {@code Window} to the front.

- * If {@code false}, hides this {@code Window}, its subcomponents, and all - * of its owned children. The {@code Window} and its subcomponents can be - * made visible again with a call to {@code #setVisible(true)}. - * @see java.awt.Component#isDisplayable - * @see java.awt.Component#setVisible - * @see java.awt.Window#toFront - * @see java.awt.Window#dispose - */ - @Override - public void setVisible(boolean b) { - if (b == true) { - Main.addTrayIcon(); - } - super.setVisible(b); - } - - @Override - public void replaced(Replacement replacement, String url, String contentType) { - int index = replacements.indexOf(replacement); - tableModel.setValueAt(replacement.lastAccess == null ? "" : format.format(replacement.lastAccess.getTime()), index, 0); - tableModel.setValueAt(new SizeItem(replacement.targetFile), index, 1); - tableModel.setValueAt(replacement.urlPattern, index, 2); - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.proxy; + +import com.jpexs.decompiler.flash.RetryTask; +import com.jpexs.decompiler.flash.RunnableIOEx; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.gui.AppFrame; +import com.jpexs.decompiler.flash.gui.AppStrings; +import com.jpexs.decompiler.flash.gui.GuiAbortRetryIgnoreHandler; +import com.jpexs.decompiler.flash.gui.Main; +import com.jpexs.decompiler.flash.gui.MainFrame; +import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.utf8.Utf8InputStreamReader; +import com.jpexs.helpers.utf8.Utf8OutputStreamWriter; +import com.jpexs.proxy.CatchedListener; +import com.jpexs.proxy.ReplacedListener; +import com.jpexs.proxy.Replacement; +import com.jpexs.proxy.Server; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Image; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.filechooser.FileFilter; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableColumnModel; +import javax.swing.table.DefaultTableModel; + +/** + * Frame with Proxy + * + * @author JPEXS + */ +public class ProxyFrame extends AppFrame implements CatchedListener, MouseListener, ReplacedListener { + + private static final String REPLACEMENTS_NAME = "replacements.cfg"; + + private JTable replacementsTable; + + private JButton switchButton = new JButton(translate("proxy.start")); + + private boolean started = false; + + private JTextField portField = new JTextField("55555"); + + private JCheckBox sniffSWFCheckBox = new JCheckBox("SWF", false); + + private JCheckBox sniffOSCheckBox = new JCheckBox("OctetStream", false); + + private JCheckBox sniffJSCheckBox = new JCheckBox("JS", false); + + private JCheckBox sniffXMLCheckBox = new JCheckBox("XML", false); + + /** + * Is server running + * + * @return True when running + */ + public boolean isRunning() { + return started; + } + + /** + * Sets port for the proxy + * + * @param port Port number + */ + public void setPort(int port) { + portField.setText(Integer.toString(port)); + } + + private static class SizeItem implements Comparable { + + String file; + + public SizeItem(String file) { + this.file = file; + } + + @Override + public String toString() { + return Helper.byteCountStr(new File(file).length(), false); + } + + @Override + public int compareTo(SizeItem o) { + return (int) (new File(file).length() - new File(o.file).length()); + } + } + + DefaultTableModel tableModel; + + private SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss"); + + /** + * List of replacements + */ + private static List replacements = new ArrayList<>(); + + /** + * Saves replacements to file for future use + */ + private static void saveReplacements() { + String replacementsFile = getReplacementsFile(); + if (replacements.isEmpty()) { + File rf = new File(replacementsFile); + if (rf.exists()) { + if (!rf.delete()) { + Logger.getLogger(ProxyFrame.class.getName()).log(Level.SEVERE, "Cannot delete replacements file"); + } + } + } else { + try (PrintWriter pw = new PrintWriter(new Utf8OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(replacementsFile))))) { + for (Replacement r : replacements) { + pw.println(r.urlPattern); + pw.println(r.targetFile); + } + } catch (IOException ex) { + Logger.getLogger(ProxyFrame.class.getName()).log(Level.SEVERE, "Exception during saving replacements", ex); + } + } + } + + /** + * Load replacements from file + */ + private static void loadReplacements() { + String replacementsFile = getReplacementsFile(); + if (!(new File(replacementsFile)).exists()) { + return; + } + replacements = new ArrayList<>(); + try (BufferedReader br = new BufferedReader(new Utf8InputStreamReader(new FileInputStream(replacementsFile)))) { + String s; + while ((s = br.readLine()) != null) { + Replacement r = new Replacement(s, br.readLine()); + replacements.add(r); + } + } catch (IOException e) { + //ignore + } + } + + private static String getReplacementsFile() { + return Configuration.getFFDecHome() + REPLACEMENTS_NAME; + } + + /** + * Constructor + * + * @param mainFrame Main frame + */ + public ProxyFrame(final MainFrame mainFrame) { + + final String[] columnNames = new String[]{ + translate("column.accessed"), + translate("column.size"), + translate("column.url")}; + + loadReplacements(); + + Object[][] data = new Object[replacements.size()][3]; + + for (int i = 0; i < replacements.size(); i++) { + Replacement r = replacements.get(i); + data[i][0] = r.lastAccess == null ? "" : format.format(r.lastAccess.getTime()); + data[i][1] = new SizeItem(r.targetFile); + data[i][2] = r.urlPattern; + } + + tableModel = new DefaultTableModel(data, columnNames) { + + @Override + public Class getColumnClass(int columnIndex) { + Class[] classes = new Class[]{String.class, SizeItem.class, String.class}; + return classes[columnIndex]; + } + + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + + }; + replacementsTable = new JTable(tableModel); + + DefaultTableCellRenderer tcr = new DefaultTableCellRenderer(); + tcr.setHorizontalAlignment(SwingConstants.RIGHT); + + replacementsTable.setDefaultRenderer(String.class, new DefaultTableCellRenderer()); + replacementsTable.setDefaultRenderer(SizeItem.class, tcr); + + replacementsTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + + replacementsTable.setRowSelectionAllowed(true); + + DefaultTableColumnModel colModel = (DefaultTableColumnModel) replacementsTable.getColumnModel(); + colModel.getColumn(0).setMaxWidth(100); + + colModel.getColumn(1).setMaxWidth(200); + + replacementsTable.setAutoCreateRowSorter(true); + + replacementsTable.setAutoCreateRowSorter(false); + + replacementsTable.addMouseListener(this); + replacementsTable.setFont(new Font("Monospaced", Font.PLAIN, 12)); + switchButton.addActionListener(this::switchStateButtonActionPerformed); + Container cnt = getContentPane(); + cnt.setLayout(new BorderLayout()); + cnt.add(new JScrollPane(replacementsTable), BorderLayout.CENTER); + + portField.setPreferredSize(new Dimension(80, portField.getPreferredSize().height)); + JPanel buttonsPanel = new JPanel(); + buttonsPanel.setLayout(new FlowLayout()); + buttonsPanel.add(new JLabel(translate("port"))); + buttonsPanel.add(portField); + buttonsPanel.add(switchButton); + cnt.add(buttonsPanel, BorderLayout.NORTH); + + JPanel buttonsPanel23 = new JPanel(); + buttonsPanel23.setLayout(new BoxLayout(buttonsPanel23, BoxLayout.Y_AXIS)); + + JPanel buttonsPanel21 = new JPanel(new FlowLayout()); + JButton openButton = new JButton(translate("open")); + openButton.addActionListener(this::openButtonActionPerformed); + buttonsPanel21.add(openButton); + JButton clearButton = new JButton(translate("clear")); + clearButton.addActionListener(this::clearButtonActionPerformed); + buttonsPanel21.add(clearButton); + JButton renameButton = new JButton(translate("rename")); + renameButton.addActionListener(this::renameButtonActionPerformed); + buttonsPanel21.add(renameButton); + JButton removeButton = new JButton(translate("remove")); + removeButton.addActionListener(this::removeButtonActionPerformed); + buttonsPanel21.add(removeButton); + + //JPanel buttonsPanel22 = new JPanel(new FlowLayout()); + JButton copyUrlButton = new JButton(translate("copy.url")); + copyUrlButton.addActionListener(this::copyUrlButtonActionPerformed); + buttonsPanel21.add(copyUrlButton); + + JButton saveAsButton = new JButton(translate("save.as")); + saveAsButton.addActionListener(this::saveAsButtonActionPerformed); + buttonsPanel21.add(saveAsButton); + + JButton replaceButton = new JButton(translate("replace")); + replaceButton.addActionListener(this::replaceButtonActionPerformed); + buttonsPanel21.add(replaceButton); + + JPanel buttonsPanel3 = new JPanel(); + buttonsPanel3.setLayout(new FlowLayout()); + buttonsPanel3.add(new JLabel(translate("sniff"))); + buttonsPanel3.add(sniffSWFCheckBox); + buttonsPanel3.add(sniffOSCheckBox); + //buttonsPanel3.add(sniffJSCheckBox); + //buttonsPanel3.add(sniffXMLCheckBox); + + buttonsPanel23.add(buttonsPanel21); + //buttonsPanel23.add(buttonsPanel22); + buttonsPanel23.add(buttonsPanel3); + + cnt.add(buttonsPanel23, BorderLayout.SOUTH); + setSize(800, 500); + View.centerScreen(this); + View.setWindowIcon(this); + setTitle(translate("dialog.title")); + this.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + setVisible(false); + Main.removeTrayIcon(); + if (mainFrame != null) { + if (mainFrame.isVisible()) { + return; + } + } + Main.showModeFrame(); + } + + /** + * Invoked when a window is iconified. + */ + @Override + public void windowIconified(WindowEvent e) { + setVisible(false); + } + }); + List images = new ArrayList<>(); + images.add(View.loadImage("proxy16")); + images.add(View.loadImage("proxy32")); + setIconImages(images); + } + + private void open() { + if (replacementsTable.getSelectedRow() > -1) { + Replacement r = replacements.get(replacementsTable.getRowSorter().convertRowIndexToModel(replacementsTable.getSelectedRow())); + Main.openFile(r.targetFile, r.urlPattern); + } + } + + private String selectExportDir() { + JFileChooser chooser = new JFileChooser(); + chooser.setCurrentDirectory(new File(Configuration.lastExportDir.get())); + chooser.setDialogTitle(translate("export.select.directory")); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setAcceptAllFileFilterUsed(false); + if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + final String selFile = Helper.fixDialogFile(chooser.getSelectedFile()).getAbsolutePath(); + Configuration.lastExportDir.set(Helper.fixDialogFile(chooser.getSelectedFile()).getAbsolutePath()); + return selFile; + } + return null; + } + + private int[] getSelectedRows() { + int[] sel = replacementsTable.getSelectedRows(); + for (int i = 0; i < sel.length; i++) { + sel[i] = replacementsTable.getRowSorter().convertRowIndexToModel(sel[i]); + } + + return sel; + } + + private void openButtonActionPerformed(ActionEvent evt) { + open(); + } + + private void saveAsButtonActionPerformed(ActionEvent evt) { + int[] sel = getSelectedRows(); + if (sel.length == 1) { + Replacement r = replacements.get(sel[0]); + JFileChooser fc = new JFileChooser(); + fc.setCurrentDirectory(new File(Configuration.lastSaveDir.get())); + String n = r.urlPattern; + if (n.contains("?")) { + n = n.substring(0, n.indexOf('?')); + } + if (n.contains("/")) { + n = n.substring(n.lastIndexOf('/')); + } + n = Helper.makeFileName(n); + fc.setSelectedFile(new File(Configuration.lastSaveDir.get(), n)); + String ext = ".swf"; + final String extension = ext; + FileFilter swfFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return (f.getName().toLowerCase().endsWith(extension)) || (f.isDirectory()); + } + + @Override + public String getDescription() { + return AppStrings.translate("filter" + extension); + } + }; + fc.setFileFilter(swfFilter); + fc.setAcceptAllFileFilterUsed(true); + JFrame f = new JFrame(); + View.setWindowIcon(f); + if (fc.showSaveDialog(f) == JFileChooser.APPROVE_OPTION) { + File file = Helper.fixDialogFile(fc.getSelectedFile()); + try { + Files.copy(new File(r.targetFile).toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ex) { + View.showMessageDialog(this, translate("error.save.as") + "\r\n" + ex.getLocalizedMessage(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + } + } else { + GuiAbortRetryIgnoreHandler handler = new GuiAbortRetryIgnoreHandler(); + File exportDir = new File(selectExportDir()); + for (int s : sel) { + final Replacement r = replacements.get(s); + String n = r.urlPattern; + if (n.contains("?")) { + n = n.substring(0, n.indexOf('?')); + } + if (n.contains("/")) { + n = n.substring(n.lastIndexOf('/')); + } + n = Helper.makeFileName(n); + int c = 2; + String n2 = n; + while (new File(exportDir, n2).exists()) { + if (n.contains(".")) { + n2 = n.substring(0, n.lastIndexOf('.')) + c + n.substring(n.lastIndexOf('.')); + c++; + } else { + n2 = n + c + ".swf"; + c++; + } + } + + final File outfile = new File(exportDir, n2); + try { + new RetryTask(new RunnableIOEx() { + @Override + public void run() throws IOException { + Files.copy(new File(r.targetFile).toPath(), outfile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + }, handler).run(); + } catch (IOException | InterruptedException ex) { + break; + } + } + } + } + + private void replaceButtonActionPerformed(ActionEvent evt) { + int[] sel = getSelectedRows(); + if (sel.length > 0) { + Replacement r = replacements.get(sel[0]); + JFileChooser fc = new JFileChooser(); + fc.setCurrentDirectory(new File(Configuration.lastOpenDir.get())); + String ext = ".swf"; + final String extension = ext; + FileFilter swfFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return (f.getName().toLowerCase().endsWith(extension)) || (f.isDirectory()); + } + + @Override + public String getDescription() { + return AppStrings.translate("filter" + extension); + } + }; + fc.setFileFilter(swfFilter); + fc.setAcceptAllFileFilterUsed(true); + JFrame f = new JFrame(); + View.setWindowIcon(f); + if (fc.showOpenDialog(f) == JFileChooser.APPROVE_OPTION) { + File file = Helper.fixDialogFile(fc.getSelectedFile()); + try { + Files.copy(file.toPath(), new File(r.targetFile).toPath(), StandardCopyOption.REPLACE_EXISTING); + tableModel.fireTableCellUpdated(sel[0], 1/*size*/); + } catch (IOException ex) { + View.showMessageDialog(f, translate("error.replace") + "\r\n" + ex.getLocalizedMessage(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + } + } + } + + private void copyUrlButtonActionPerformed(ActionEvent evt) { + int[] sel = getSelectedRows(); + StringBuilder copyText = new StringBuilder(); + for (int sc : sel) { + Replacement r = replacements.get(sc); + if (copyText.length() > 0) { + copyText.append(System.lineSeparator()); + } + copyText.append(r.urlPattern); + } + + if (copyText.length() > 0) { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + StringSelection stringSelection = new StringSelection(copyText.toString()); + clipboard.setContents(stringSelection, null); + } + } + + private void renameButtonActionPerformed(ActionEvent evt) { + int[] sel = getSelectedRows(); + if (sel.length > 0) { + Replacement r = replacements.get(sel[0]); + String s = View.showInputDialog("URL", r.urlPattern); + if (s != null) { + r.urlPattern = s; + tableModel.setValueAt(s, sel[0], 2/*url*/); + } + } + } + + private void clearButtonActionPerformed(ActionEvent evt) { + for (Replacement r : replacements) { + File f; + try { + f = (new File(Main.tempFile(r.targetFile))); + if (f.exists()) { + f.delete(); + } + } catch (IOException ex) { + Logger.getLogger(ProxyFrame.class.getName()).log(Level.SEVERE, null, ex); + } + } + tableModel.setRowCount(0); + replacements.clear(); + saveReplacements(); + } + + private void removeButtonActionPerformed(ActionEvent evt) { + int[] sel = getSelectedRows(); + Arrays.sort(sel); + for (int i = sel.length - 1; i >= 0; i--) { + tableModel.removeRow(sel[i]); + Replacement r = replacements.remove(sel[i]); + saveReplacements(); + File f = (new File(r.targetFile)); + if (f.exists()) { + f.delete(); + } + } + } + + private void switchStateButtonActionPerformed(ActionEvent evt) { + Main.switchProxy(); + } + + /** + * Switch proxy state + */ + public void switchState() { + started = !started; + if (started) { + int port = 0; + try { + port = Integer.parseInt(portField.getText()); + } catch (NumberFormatException nfe) { + } + if ((port <= 0) || (port > 65535)) { + View.showMessageDialog(this, translate("error.port"), translate("error"), JOptionPane.ERROR_MESSAGE); + started = false; + return; + } + List catchedContentTypes = new ArrayList<>(); + catchedContentTypes.add("application/x-shockwave-flash"); + catchedContentTypes.add("application/x-javascript"); + catchedContentTypes.add("application/javascript"); + catchedContentTypes.add("text/javascript"); + catchedContentTypes.add("application/json"); + catchedContentTypes.add("text/xml"); + catchedContentTypes.add("application/xml"); + catchedContentTypes.add("application/octet-stream"); + if (!Server.startServer(port, replacements, catchedContentTypes, this, this)) { + JOptionPane.showMessageDialog(this, translate("error.start.server").replace("%port%", "" + port), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + started = false; + return; + } + switchButton.setText(translate("proxy.stop")); + portField.setEditable(false); + } else { + Server.stopServer(); + switchButton.setText(translate("proxy.start")); + portField.setEditable(true); + } + } + + /** + * Mouse clicked event + * + * @param e event + */ + @Override + public void mouseClicked(MouseEvent e) { + if (e.getSource() == replacementsTable) { + if (e.getClickCount() == 2) { + open(); + } + } + } + + /** + * Mouse pressed event + * + * @param e event + */ + @Override + public void mousePressed(MouseEvent e) { + } + + /** + * Mouse released event + * + * @param e event + */ + @Override + public void mouseReleased(MouseEvent e) { + } + + /** + * Mouse entered event + * + * @param e event + */ + @Override + public void mouseEntered(MouseEvent e) { + } + + /** + * Mouse exited event + * + * @param e event + */ + @Override + public void mouseExited(MouseEvent e) { + } + + /** + * Method called when specified contentType is received + * + * @param contentType Content type + * @param url URL of the method + * @param data Data stream + * @return replacement data + */ + @Override + public byte[] catched(String contentType, String url, InputStream data) { + boolean swfOnly = false; + if (contentType.contains(";")) { + contentType = contentType.substring(0, contentType.indexOf(';')); + } + if ((!sniffSWFCheckBox.isSelected()) && (contentType.equals("application/x-shockwave-flash"))) { + return null; + } + if ((!sniffJSCheckBox.isSelected()) && (contentType.equals("application/javascript") || contentType.equals("application/x-javascript") || contentType.equals("text/javascript") || contentType.equals("application/json"))) { + return null; + } + if ((!sniffXMLCheckBox.isSelected()) && (contentType.equals("application/xml") || contentType.equals("text/xml"))) { + return null; + } + if ((!sniffOSCheckBox.isSelected()) && (contentType.equals("application/octet-stream"))) { + return null; + } + + byte[] result = null; + + boolean cont = false; + for (Replacement r : replacements) { + if (r.matches(url)) { + cont = true; + break; + } + } + if (!cont) { + try { + byte[] hdr = new byte[3]; + if (data.read(hdr) != 3) { + throw new IOException(); + } + + String shdr = new String(hdr); + if (swfOnly && ((!shdr.equals("FWS")) && (!shdr.equals("CWS")) && (!shdr.equals("ZWS")))) { + return null; //NOT SWF + } + + String tempFilePath = Main.tempFile(url); + data.reset(); + byte[] dataArray = Helper.readStream(data); + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(new File(tempFilePath)))) { + fos.write(dataArray); + } + + result = SWFDecompilerPlugin.fireProxyFileCatched(dataArray); + + Replacement r = new Replacement(url, tempFilePath); + r.lastAccess = Calendar.getInstance(); + replacements.add(r); + saveReplacements(); + tableModel.addRow(new Object[]{ + r.lastAccess == null ? "" : format.format(r.lastAccess.getTime()), + new SizeItem(r.targetFile), + r.urlPattern + }); + } catch (IOException e) { + } + } + + return result; + } + + /** + * Shows or hides this {@code Window} depending on the value of parameter + * {@code b}. + * + * @param b if {@code true}, makes the {@code Window} visible, otherwise + * hides the {@code Window}. If the {@code Window} and/or its owner are not + * yet displayable, both are made displayable. The {@code Window} will be + * validated prior to being made visible. If the {@code Window} is already + * visible, this will bring the {@code Window} to the front.

+ * If {@code false}, hides this {@code Window}, its subcomponents, and all + * of its owned children. The {@code Window} and its subcomponents can be + * made visible again with a call to {@code #setVisible(true)}. + * @see java.awt.Component#isDisplayable + * @see java.awt.Component#setVisible + * @see java.awt.Window#toFront + * @see java.awt.Window#dispose + */ + @Override + public void setVisible(boolean b) { + if (b == true) { + Main.addTrayIcon(); + } + super.setVisible(b); + } + + @Override + public void replaced(Replacement replacement, String url, String contentType) { + int index = replacements.indexOf(replacement); + tableModel.setValueAt(replacement.lastAccess == null ? "" : format.format(replacement.lastAccess.getTime()), index, 0); + tableModel.setValueAt(new SizeItem(replacement.targetFile), index, 1); + tableModel.setValueAt(replacement.urlPattern, index, 2); + } +}