diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb13ab7c..5512ef2ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ All notable changes to this project will be documented in this file. - ABC Explorer - items with zero usages are semi-transparent - ABC Explorer - copy path to clipboard - ABC Explorer - Go to path via `Ctrl + G` +- Optimize ABC action (remove unused items) - available through ABC Explorer ### Fixed - Debugger - getting children of top level variables @@ -57,6 +58,7 @@ All notable changes to this project will be documented in this file. - [#2239] Exporting TTF font on Linux - [PR193] Quoting JAR file in ffdec.sh - Refreshing class/exportname association on SymbolClass/ExportAssets deletion +- Outputstreams position calculation (ABCOutputStream, ...) ### Changed - [#2185] MochiCrypt no longer offered for auto decrypt, user needs to choose variant from "Use unpacker" menu diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java index f404b80fa..7f08a8b7d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java @@ -145,6 +145,13 @@ public class SWFOutputStream extends OutputStream { pos += b.length; } + @Override + public void write(byte[] b, int off, int len) throws IOException { + alignByte(); + os.write(b, off, len); + pos += len; + } + public void write(ByteArrayRange b) throws IOException { alignByte(); os.write(b.getArray(), b.getPos(), b.getLength()); 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 5c9350947..8ade9e971 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 @@ -63,6 +63,7 @@ import com.jpexs.decompiler.flash.treeitems.Openable; import com.jpexs.decompiler.flash.treeitems.OpenableList; import com.jpexs.decompiler.flash.types.annotations.Internal; import com.jpexs.decompiler.graph.DottedChain; +import com.jpexs.helpers.Helper; import com.jpexs.helpers.utf8.Utf8PrintWriter; import java.io.File; import java.io.IOException; @@ -1161,8 +1162,7 @@ public class ABC implements Openable { for (int i = 0; i < bodies.size(); i++) { output.println("MethodBody[" + i + "]:"); //+ bodies[i].toString(this, constants, method_info)); } - } - + } public List findMultinameDefinition(int multinameIndex) { List usages = findMultinameUsage(multinameIndex, false); @@ -1184,7 +1184,7 @@ public class ABC implements Openable { } return ret; } - + public List findMultinameUsage(int multinameIndex, boolean exactMatch) { MultinameUsageDetector det = new MultinameUsageDetector(); List retUsage = det.findMultinameUsage(this, multinameIndex, exactMatch); @@ -1270,8 +1270,6 @@ public class ABC implements Openable { } } - - public int findMethodInfoByName(int classId, String methodNameWithSuffix) { if (classId > -1) { for (Trait t : instance_info.get(classId).instance_traits.traits) { @@ -1728,10 +1726,7 @@ public class ABC implements Openable { s--; } } - getSwf().clearAbcListCache(); - getSwf().clearScriptCache(); - getMethodIndexing(); - getSwf().getAbcIndex().refreshAbc(this); + clearAllCaches(); fireChanged(); } @@ -2275,5 +2270,13 @@ public class ABC implements Openable { public long getDataSize() { return dataSize; - } + } + + public void clearAllCaches() { + resetMethodIndexing(); + getSwf().clearAbcListCache(); + getSwf().clearScriptCache(); + getMethodIndexing(); + getSwf().getAbcIndex().refreshAbc(this); + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCOutputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCOutputStream.java index 912f7219c..6dc63253c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCOutputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCOutputStream.java @@ -40,7 +40,7 @@ public class ABCOutputStream extends OutputStream { private final OutputStream os; private long position = 0L; - + public ABCOutputStream(OutputStream os) { this.os = os; } @@ -57,13 +57,13 @@ public class ABCOutputStream extends OutputStream { @Override public void write(byte[] data) throws IOException { - super.write(data); - position += data.length; + os.write(data); + position += data.length; } @Override public void write(byte[] b, int off, int len) throws IOException { - super.write(b, off, len); + os.write(b, off, len); position += len; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AbcIndexing.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AbcIndexing.java index a899527c3..66464db4e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AbcIndexing.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AbcIndexing.java @@ -161,8 +161,10 @@ public final class AbcIndexing { Integer builtInNs = builtInNsPerAbc.get(abc); if (builtInNs == null) { - builtInIndex = abc.constants.getNamespaceId(Namespace.KIND_NAMESPACE, BUILT_IN_NS, 0, true); - builtInNsPerAbc.put(abc, builtInIndex); + //we need to avoid modifying the ABC + /* builtInIndex = abc.constants.getNamespaceId(Namespace.KIND_NAMESPACE, BUILT_IN_NS, 0, true); + builtInNsPerAbc.put(abc, builtInIndex);*/ + builtInIndex = Integer.MIN_VALUE; //?? } else { builtInIndex = builtInNs; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/usages/simple/ABCOptimizer.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/usages/simple/ABCOptimizer.java new file mode 100644 index 000000000..9c8786c8d --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/usages/simple/ABCOptimizer.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2010-2023 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.usages.simple; + +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.ABCOutputStream; +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.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.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.tags.Tag; +import com.jpexs.helpers.NulStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class ABCOptimizer { + + public void optimize(ABC abc) { + ABCSimpleUsageDetector usageDetector = new ABCSimpleUsageDetector(abc); + usageDetector.detect(); + Map>> usages = usageDetector.getUsages(); + Map> notReferencedIndices = new HashMap<>(); + Map> replaceMap = new HashMap<>(); + + for (ABCSimpleUsageDetector.ItemKind kind : usages.keySet()) { + List> usagesList = usages.get(kind); + notReferencedIndices.put(kind, new ArrayList<>()); + replaceMap.put(kind, new HashMap<>()); + if (kind.hasReservedZeroIndex()) { + replaceMap.get(kind).put(0, 0); + } + int pos = kind.hasReservedZeroIndex() ? 1 : 0; + for (int i = pos; i < usagesList.size(); i++) { + if (usagesList.get(i).isEmpty()) { + notReferencedIndices.get(kind).add(i); + } else { + replaceMap.get(kind).put(i, pos); + pos++; + } + } + } + + /* + for (ABCSimpleUsageDetector.ItemKind kind : replaceMap.keySet()) { + System.err.println("---------"); + System.err.println("" + kind + " map:"); + for (int key : replaceMap.get(kind).keySet()) { + System.err.println(" " + key + " => " + replaceMap.get(kind).get(key)); + } + System.err.println(" " + kind + " not referenced:"); + for (int key : notReferencedIndices.get(kind)) { + System.err.println(" " + key); + } + } + System.err.println("==================="); */ + + for (int i = 0; i < abc.script_info.size(); i++) { + ScriptInfo m = abc.script_info.get(i); + m.init_index = handleReplace(ABCSimpleUsageDetector.ItemKind.METHODINFO, m.init_index, replaceMap); + walkTraits(abc, m.traits, replaceMap); + } + + for (int i = 0; i < abc.method_info.size(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.METHODINFO).contains(i)) { + continue; + } + MethodInfo m = abc.method_info.get(i); + m.name_index = handleReplace(ABCSimpleUsageDetector.ItemKind.STRING, m.name_index, replaceMap); + if (m.flagHas_paramnames()) { + for (int j = 0; j < m.paramNames.length; j++) { + m.paramNames[j] = handleReplace(ABCSimpleUsageDetector.ItemKind.STRING, m.paramNames[j], replaceMap); + } + } + if (m.flagHas_optional()) { + for (int j = 0; j < m.optional.length; j++) { + m.optional[j].value_index = handleReplaceValueKind(m.optional[j].value_kind, m.optional[j].value_index, replaceMap); + } + } + + for (int j = 0; j < m.param_types.length; j++) { + m.param_types[j] = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, m.param_types[j], replaceMap); + } + + m.ret_type = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, m.ret_type, replaceMap); + } + + for (int i = 0; i < abc.metadata_info.size(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.METADATAINFO).contains(i)) { + continue; + } + MetadataInfo m = abc.metadata_info.get(i); + m.name_index = handleReplace(ABCSimpleUsageDetector.ItemKind.STRING, m.name_index, replaceMap); + for (int j = 0; j < m.keys.length; j++) { + m.keys[j] = handleReplace(ABCSimpleUsageDetector.ItemKind.STRING, m.keys[j], replaceMap); + } + for (int j = 0; j < m.values.length; j++) { + m.values[j] = handleReplace(ABCSimpleUsageDetector.ItemKind.STRING, m.values[j], replaceMap); + } + } + + for (int i = 1; i < abc.constants.getMultinameCount(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.MULTINAME).contains(i)) { + continue; + } + Multiname m = abc.constants.getMultiname(i); + if (m.hasOwnName()) { + m.name_index = handleReplace(ABCSimpleUsageDetector.ItemKind.STRING, m.name_index, replaceMap); + } + if (m.hasOwnNamespace()) { + m.namespace_index = handleReplace(ABCSimpleUsageDetector.ItemKind.NAMESPACE, m.namespace_index, replaceMap); + } + if (m.hasOwnNamespaceSet()) { + m.namespace_set_index = handleReplace(ABCSimpleUsageDetector.ItemKind.NAMESPACESET, m.namespace_set_index, replaceMap); + } + if (m.kind == Multiname.TYPENAME) { + m.qname_index = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, m.qname_index, replaceMap); + for (int j = 0; j < m.params.length; j++) { + m.params[j] = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, m.params[j], replaceMap); + } + } + } + + for (int i = 1; i < abc.constants.getNamespaceCount(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.NAMESPACE).contains(i)) { + continue; + } + Namespace m = abc.constants.getNamespace(i); + m.name_index = handleReplace(ABCSimpleUsageDetector.ItemKind.STRING, m.name_index, replaceMap); + } + + for (int i = 1; i < abc.constants.getNamespaceSetCount(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.MULTINAME).contains(i)) { + continue; + } + NamespaceSet m = abc.constants.getNamespaceSet(i); + for (int j = 0; j < m.namespaces.length; j++) { + m.namespaces[j] = handleReplace(ABCSimpleUsageDetector.ItemKind.NAMESPACE, m.namespaces[j], replaceMap); + } + } + + for (int i = 0; i < abc.bodies.size(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.METHODBODY).contains(i)) { + continue; + } + MethodBody m = abc.bodies.get(i); + for (int j = 0; j < m.exceptions.length; j++) { + m.exceptions[j].name_index = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, m.exceptions[j].name_index, replaceMap); + m.exceptions[j].type_index = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, m.exceptions[j].type_index, replaceMap); + } + m.method_info = handleReplace(ABCSimpleUsageDetector.ItemKind.METHODINFO, m.method_info, replaceMap); + walkTraits(abc, m.traits, replaceMap); + AVM2Code acode = m.getCode(); + List code = acode.code; + boolean bodyModified = false; + for (int ip = 0; ip < code.size(); ip++) { + AVM2Instruction ins = code.get(ip); + for (int operandIndex = 0; operandIndex < ins.definition.operands.length; operandIndex++) { + int oldOperand = ins.operands[operandIndex]; + switch (ins.definition.operands[operandIndex]) { + case AVM2Code.DAT_CLASS_INDEX: + ins.operands[operandIndex] = handleReplace(ABCSimpleUsageDetector.ItemKind.CLASS, ins.operands[operandIndex], replaceMap); + break; + case AVM2Code.DAT_DOUBLE_INDEX: + ins.operands[operandIndex] = handleReplace(ABCSimpleUsageDetector.ItemKind.DOUBLE, ins.operands[operandIndex], replaceMap); + break; + case AVM2Code.DAT_INT_INDEX: + ins.operands[operandIndex] = handleReplace(ABCSimpleUsageDetector.ItemKind.INT, ins.operands[operandIndex], replaceMap); + break; + case AVM2Code.DAT_METHOD_INDEX: + ins.operands[operandIndex] = handleReplace(ABCSimpleUsageDetector.ItemKind.METHODINFO, ins.operands[operandIndex], replaceMap); + break; + case AVM2Code.DAT_MULTINAME_INDEX: + ins.operands[operandIndex] = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, ins.operands[operandIndex], replaceMap); + break; + case AVM2Code.DAT_NAMESPACE_INDEX: + ins.operands[operandIndex] = handleReplace(ABCSimpleUsageDetector.ItemKind.NAMESPACE, ins.operands[operandIndex], replaceMap); + break; + case AVM2Code.DAT_STRING_INDEX: + ins.operands[operandIndex] = handleReplace(ABCSimpleUsageDetector.ItemKind.STRING, ins.operands[operandIndex], replaceMap); + break; + case AVM2Code.DAT_UINT_INDEX: + ins.operands[operandIndex] = handleReplace(ABCSimpleUsageDetector.ItemKind.UINT, ins.operands[operandIndex], replaceMap); + break; + } + int newOperand = ins.operands[operandIndex]; + if (oldOperand != newOperand) { + bodyModified = true; + int byteDelta = ABCOutputStream.getU30ByteLength(newOperand) - ABCOutputStream.getU30ByteLength(oldOperand); + if (byteDelta != 0) { + acode.updateInstructionByteCount(ip, byteDelta, m); + } + } + } + } + if (bodyModified) { + m.setModified(); + } + } + + AVM2ConstantPool newCpool = new AVM2ConstantPool(); + for (int i = 1; i < abc.constants.getIntCount(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.INT).contains(i)) { + continue; + } + newCpool.addInt(abc.constants.getInt(i)); + } + for (int i = 1; i < abc.constants.getUIntCount(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.UINT).contains(i)) { + continue; + } + newCpool.addUInt(abc.constants.getUInt(i)); + } + for (int i = 1; i < abc.constants.getDoubleCount(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.DOUBLE).contains(i)) { + continue; + } + newCpool.addDouble(abc.constants.getDouble(i)); + } + + for (int i = 1; i < abc.constants.getStringCount(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.STRING).contains(i)) { + continue; + } + newCpool.addString(abc.constants.getString(i)); + } + + for (int i = 1; i < abc.constants.getNamespaceCount(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.NAMESPACE).contains(i)) { + continue; + } + newCpool.addNamespace(abc.constants.getNamespace(i)); + } + + for (int i = 1; i < abc.constants.getNamespaceSetCount(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.NAMESPACESET).contains(i)) { + continue; + } + newCpool.addNamespaceSet(abc.constants.getNamespaceSet(i)); + } + + for (int i = 1; i < abc.constants.getMultinameCount(); i++) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.MULTINAME).contains(i)) { + continue; + } + newCpool.addMultiname(abc.constants.getMultiname(i)); + } + abc.constants = newCpool; + + for (int i = abc.metadata_info.size() - 1; i >= 0; i--) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.METADATAINFO).contains(i)) { + abc.metadata_info.remove(i); + } + } + for (int i = abc.bodies.size() - 1; i >= 0; i--) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.METHODBODY).contains(i)) { + abc.bodies.remove(i); + } + } + + for (int i = abc.method_info.size() - 1; i >= 0; i--) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.METHODINFO).contains(i)) { + abc.method_info.remove(i); + } + } + + for (int i = abc.instance_info.size() - 1; i >= 0; i--) { + if (notReferencedIndices.get(ABCSimpleUsageDetector.ItemKind.CLASS).contains(i)) { + abc.instance_info.remove(i); + abc.class_info.remove(i); + } + } + abc.clearAllCaches(); + try { + abc.saveToStream(new NulStream()); //To recalculate dataSize + } catch (IOException ex) { + //ignore + } + if (abc.parentTag != null) { + ((Tag) abc.parentTag).setModified(true); + } + abc.fireChanged(); + } + + private int handleReplace(ABCSimpleUsageDetector.ItemKind kind, int index, Map> replaceMap) { + if (!replaceMap.get(kind).containsKey(index)) { + return index; + } + return replaceMap.get(kind).get(index); + } + + private int handleReplaceValueKind(int value_kind, int value_index, Map> replaceMap) { + switch (value_kind) { + case ValueKind.CONSTANT_Int: + return handleReplace(ABCSimpleUsageDetector.ItemKind.INT, value_index, replaceMap); + case ValueKind.CONSTANT_UInt: + return handleReplace(ABCSimpleUsageDetector.ItemKind.UINT, value_index, replaceMap); + case ValueKind.CONSTANT_Double: + return handleReplace(ABCSimpleUsageDetector.ItemKind.DOUBLE, value_index, replaceMap); + case ValueKind.CONSTANT_Utf8: + return handleReplace(ABCSimpleUsageDetector.ItemKind.STRING, value_index, replaceMap); + case ValueKind.CONSTANT_Namespace: + case ValueKind.CONSTANT_PackageNamespace: + case ValueKind.CONSTANT_PackageInternalNs: + case ValueKind.CONSTANT_ProtectedNamespace: + case ValueKind.CONSTANT_ExplicitNamespace: + case ValueKind.CONSTANT_StaticProtectedNs: + case ValueKind.CONSTANT_PrivateNs: + return handleReplace(ABCSimpleUsageDetector.ItemKind.NAMESPACE, value_index, replaceMap); + } + return value_index; + } + + private void walkTraits(ABC abc, Traits traits, Map> replaceMap) { + for (Trait t : traits.traits) { + if ((t.kindFlags & Trait.ATTR_Metadata) > 0) { + for (int i = 0; i < t.metadata.length; i++) { + t.metadata[i] = handleReplace(ABCSimpleUsageDetector.ItemKind.METADATAINFO, t.metadata[i], replaceMap); + } + } + t.name_index = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, t.name_index, replaceMap); + if (t instanceof TraitMethodGetterSetter) { + TraitMethodGetterSetter tmgs = (TraitMethodGetterSetter) t; + tmgs.method_info = handleReplace(ABCSimpleUsageDetector.ItemKind.METHODINFO, tmgs.method_info, replaceMap); + } + if (t instanceof TraitFunction) { + TraitFunction tf = (TraitFunction) t; + tf.method_info = handleReplace(ABCSimpleUsageDetector.ItemKind.METHODINFO, tf.method_info, replaceMap); + } + if (t instanceof TraitSlotConst) { + TraitSlotConst tsc = (TraitSlotConst) t; + tsc.type_index = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, tsc.type_index, replaceMap); + tsc.value_index = handleReplaceValueKind(tsc.value_kind, tsc.value_index, replaceMap); + } + if (t instanceof TraitClass) { + TraitClass tc = (TraitClass) t; + int classIndex = tc.class_info; + InstanceInfo ii = abc.instance_info.get(classIndex); + if ((ii.flags & InstanceInfo.CLASS_PROTECTEDNS) != 0) { + ii.protectedNS = handleReplace(ABCSimpleUsageDetector.ItemKind.NAMESPACE, ii.protectedNS, replaceMap); + } + ii.name_index = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, ii.name_index, replaceMap); + ii.super_index = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, ii.super_index, replaceMap); + for (int i = 0; i < ii.interfaces.length; i++) { + ii.interfaces[i] = handleReplace(ABCSimpleUsageDetector.ItemKind.MULTINAME, ii.interfaces[i], replaceMap); + } + ii.iinit_index = handleReplace(ABCSimpleUsageDetector.ItemKind.METHODINFO, ii.iinit_index, replaceMap); + walkTraits(abc, ii.instance_traits, replaceMap); + + ClassInfo ci = abc.class_info.get(classIndex); + ci.cinit_index = handleReplace(ABCSimpleUsageDetector.ItemKind.METHODINFO, ci.cinit_index, replaceMap); + walkTraits(abc, ci.static_traits, replaceMap); + + tc.class_info = handleReplace(ABCSimpleUsageDetector.ItemKind.CLASS, tc.class_info, replaceMap); + } + } + } + + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/usages/simple/ABCSimpleUsageDetector.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/usages/simple/ABCSimpleUsageDetector.java index 50cc52374..580c64aff 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/usages/simple/ABCSimpleUsageDetector.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/usages/simple/ABCSimpleUsageDetector.java @@ -46,29 +46,41 @@ import java.util.Stack; public class ABCSimpleUsageDetector { public static enum ItemKind { - INT, - UINT, - DOUBLE, - STRING, - NAMESPACE, - NAMESPACESET, - MULTINAME, - METADATAINFO, - METHODINFO, - METHODBODY, - CLASS + INT(true), + UINT(true), + DOUBLE(true), + STRING(true), + NAMESPACE(true), + NAMESPACESET(true), + MULTINAME(true), + METADATAINFO(false), + METHODINFO(false), + METHODBODY(false), + CLASS(false); + + private boolean reserveZeroIndex; + + private ItemKind (boolean reserveZeroIndex) { + this.reserveZeroIndex = reserveZeroIndex; + } + + public boolean hasReservedZeroIndex() { + return reserveZeroIndex; + } } - private final Map>> usages = new HashMap<>(); + private final Map>> usages = new HashMap<>(); + private final Map> zeroUsages = new HashMap<>(); + private final ABC abc; public ABCSimpleUsageDetector(ABC abc) { this.abc = abc; } - private void initUsages(ItemKind kind, int itemCount, boolean atleastOne) { + private void initUsages(ItemKind kind, int itemCount) { List> list = new ArrayList<>(); - if (atleastOne && itemCount == 0) { + if (kind.hasReservedZeroIndex() && itemCount == 0) { itemCount = 1; } for (int i = 0; i < itemCount; i++) { @@ -80,17 +92,17 @@ public class ABCSimpleUsageDetector { public void detect() { usages.clear(); - initUsages(ItemKind.INT, abc.constants.getIntCount(), true); - initUsages(ItemKind.UINT, abc.constants.getUIntCount(), true); - initUsages(ItemKind.DOUBLE, abc.constants.getDoubleCount(), true); - initUsages(ItemKind.STRING, abc.constants.getStringCount(), true); - initUsages(ItemKind.NAMESPACE, abc.constants.getNamespaceCount(), true); - initUsages(ItemKind.NAMESPACESET, abc.constants.getNamespaceSetCount(), true); - initUsages(ItemKind.MULTINAME, abc.constants.getMultinameCount(), true); - initUsages(ItemKind.METADATAINFO, abc.metadata_info.size(), false); - initUsages(ItemKind.METHODINFO, abc.method_info.size(), false); - initUsages(ItemKind.METHODBODY, abc.bodies.size(), false); - initUsages(ItemKind.CLASS, abc.class_info.size(), false); + initUsages(ItemKind.INT, abc.constants.getIntCount()); + initUsages(ItemKind.UINT, abc.constants.getUIntCount()); + initUsages(ItemKind.DOUBLE, abc.constants.getDoubleCount()); + initUsages(ItemKind.STRING, abc.constants.getStringCount()); + initUsages(ItemKind.NAMESPACE, abc.constants.getNamespaceCount()); + initUsages(ItemKind.NAMESPACESET, abc.constants.getNamespaceSetCount()); + initUsages(ItemKind.MULTINAME, abc.constants.getMultinameCount()); + initUsages(ItemKind.METADATAINFO, abc.metadata_info.size()); + initUsages(ItemKind.METHODINFO, abc.method_info.size()); + initUsages(ItemKind.METHODBODY, abc.bodies.size()); + initUsages(ItemKind.CLASS, abc.class_info.size()); ABCWalker walker = new ABCWalker() { protected void handleUsageNamespace(int index, String usageDescription) { @@ -417,6 +429,16 @@ public class ABCSimpleUsageDetector { } }; walker.walkABC(abc, false); + + zeroUsages.clear(); + for (ItemKind kind : usages.keySet()) { + zeroUsages.put(kind, new ArrayList<>()); + for (int i = kind.hasReservedZeroIndex() ? 1 : 0; i < usages.get(kind).size(); i++) { + if (usages.get(kind).get(i).isEmpty()) { + zeroUsages.get(kind).add(i); + } + } + } } /** @@ -445,4 +467,24 @@ public class ABCSimpleUsageDetector { public List> getUsages(ItemKind kind) { return Collections.unmodifiableList(usages.get(kind)); } + + public Map> getZeroUsages() { + return Collections.unmodifiableMap(zeroUsages); + } + + public List getZeroUsages(ItemKind kind) { + return zeroUsages.get(kind); + } + + public int getZeroUsagesCount() { + int cnt = 0; + for (ItemKind kind : zeroUsages.keySet()) { + cnt += zeroUsages.get(kind).size(); + } + return cnt; + } + + public int getZeroUsagesCount(ItemKind kind) { + return zeroUsages.get(kind).size(); + } } 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 614ee4c6a..9539fa1c6 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 @@ -185,6 +185,16 @@ public class Amf3OutputStream extends OutputStream { os.write(v); } + @Override + public void write(byte[] b) throws IOException { + os.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + os.write(b, off, len); + } + private void writeArray(ArrayType val, Map serializers, List stringTable, List traitsTable, List objectTable) throws IOException, NoSerializerExistsException { int objectIndex = objectTable.indexOf(val); if (objectIndex == -1) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index b48d224af..ec19bb04d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -1005,6 +1005,10 @@ public final class Configuration { @ConfigurationCategory("export") public static ConfigurationItem lastExportTransparentBackground = null; + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("script") + public static ConfigurationItem warningAbcOptimize = null; + private enum OSId { WINDOWS, OSX, UNIX } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/flv/FLVOutputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/flv/FLVOutputStream.java index 0a416fd9b..f5c916905 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/flv/FLVOutputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/flv/FLVOutputStream.java @@ -56,6 +56,20 @@ public class FLVOutputStream extends OutputStream { pos++; } + @Override + public void write(byte[] b) throws IOException { + alignByte(); + os.write(b); + pos += b.length; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + alignByte(); + os.write(b, off, len); + pos += len; + } + private void alignByte() throws IOException { if (bitPos > 0) { bitPos = 0; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/gfx/GFxOutputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/gfx/GFxOutputStream.java index 90562eae8..084ee49c8 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/gfx/GFxOutputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/gfx/GFxOutputStream.java @@ -117,6 +117,18 @@ public class GFxOutputStream extends OutputStream { pos++; } + @Override + public void write(byte[] b) throws IOException { + os.write(b); + pos += b.length; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + os.write(b, off, len); + pos += len; + } + /** * Writes SI32 (Signed 32bit integer) value to the stream * diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/NulStream.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/NulStream.java index 314d0dc9d..a8f61ca12 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/helpers/NulStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/NulStream.java @@ -16,6 +16,7 @@ */ package com.jpexs.helpers; +import java.io.IOException; import java.io.OutputStream; /** @@ -26,5 +27,5 @@ public class NulStream extends OutputStream { @Override public void write(int i) { - } + } } diff --git a/src/com/jpexs/decompiler/flash/gui/abc/ABCExplorerDialog.java b/src/com/jpexs/decompiler/flash/gui/abc/ABCExplorerDialog.java index 795b8ed2e..aa10382af 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/ABCExplorerDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/ABCExplorerDialog.java @@ -38,10 +38,14 @@ 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.simple.ABCOptimizer; import com.jpexs.decompiler.flash.abc.usages.simple.ABCSimpleUsageDetector; +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.FasterScrollPane; +import com.jpexs.decompiler.flash.gui.Main; import com.jpexs.decompiler.flash.gui.MainPanel; import com.jpexs.decompiler.flash.gui.View; import com.jpexs.decompiler.flash.gui.ViewMessages; @@ -87,10 +91,12 @@ import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.ImageIcon; import javax.swing.InputMap; +import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenuItem; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRootPane; @@ -129,6 +135,8 @@ public class ABCExplorerDialog extends AppDialog { private ABCSimpleUsageDetector usageDetector = null; + private JButton optimizeButton = new JButton(View.getIcon("optimize16")); + private JTable usagesTable = new JTable(new DefaultTableModel()) { @Override public boolean isCellEditable(int row, int column) { @@ -141,8 +149,8 @@ public class ABCExplorerDialog extends AppDialog { this.mainPanel = mainPanel; Container cnt = getContentPane(); cnt.setLayout(new BorderLayout()); - JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - topPanel.add(new JLabel(translate("abc"))); + JPanel topLeftPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + topLeftPanel.add(new JLabel(translate("abc"))); int selectedIndex = 0; int frame = 1; if (openable instanceof SWF) { @@ -178,7 +186,7 @@ public class ABCExplorerDialog extends AppDialog { Dimension abcComboBoxSize = new Dimension(500, abcComboBox.getPreferredSize().height); abcComboBox.setMinimumSize(abcComboBoxSize); abcComboBox.setPreferredSize(abcComboBoxSize); - topPanel.add(abcComboBox); + topLeftPanel.add(abcComboBox); abcComboBox.addActionListener(this::abcComboBoxActionPerformed); } else if (openable instanceof ABC) { @@ -199,7 +207,17 @@ public class ABCExplorerDialog extends AppDialog { }; tagInfoLabel = new JLabel(); - topPanel.add(tagInfoLabel); + topLeftPanel.add(tagInfoLabel); + + optimizeButton.setToolTipText(translate("button.optimize")); + optimizeButton.addActionListener(this::optimizeActionPerformed); + + JPanel topRightPanel = new JPanel(new FlowLayout()); + topRightPanel.add(optimizeButton); + + JPanel topPanel = new JPanel(new BorderLayout()); + topPanel.add(topLeftPanel, BorderLayout.WEST); + topPanel.add(topRightPanel, BorderLayout.EAST); mainTabbedPane = new JTabbedPane(); cpTabbedPane = new JTabbedPane(); @@ -426,6 +444,9 @@ public class ABCExplorerDialog extends AppDialog { ABCSimpleUsageDetector newUsageDetector = new ABCSimpleUsageDetector(getSelectedAbc()); newUsageDetector.detect(); usageDetector = newUsageDetector; + int zeroUsages = newUsageDetector.getZeroUsagesCount(); + optimizeButton.setText("(" + zeroUsages + ")"); + optimizeButton.setEnabled(zeroUsages > 0); } private JTree getCurrentTree() { @@ -2630,4 +2651,24 @@ public class ABCExplorerDialog extends AppDialog { return this; } } + + private void optimizeActionPerformed(ActionEvent e) { + ABC abc = getSelectedAbc(); + if (abc != null) { + if (ViewMessages.showConfirmDialog(this, translate("warning.optimize"), AppStrings.translate("message.warning"), JOptionPane.OK_CANCEL_OPTION, Configuration.warningAbcOptimize, JOptionPane.OK_OPTION) != JOptionPane.OK_OPTION) { + return; + } + int mainIndex = mainTabbedPane.getSelectedIndex(); + int cpIndex = cpTabbedPane.getSelectedIndex(); + ABCOptimizer optimizer = new ABCOptimizer(); + optimizer.optimize(abc); + if (cpIndex > -1) { + cpTabbedPane.setSelectedIndex(cpIndex); + } + if (mainIndex > -1) { + mainTabbedPane.setSelectedIndex(mainIndex); + } + Main.getMainFrame().getPanel().refreshTree(); + } + } } diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/optimize16.png b/src/com/jpexs/decompiler/flash/gui/graphics/optimize16.png new file mode 100644 index 000000000..5ca8dbaba Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/optimize16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index 973412fc4..a80ad41e1 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -768,4 +768,8 @@ config.name.previewResampleSound = Resample in sound previews config.description.previewResampleSound = Resample to 44kHz in sound previews config.name.lastExportTransparentBackground = Last setting of ignoring background color in frame export -config.description.lastExportTransparentBackground = Last setting of ignoring background color for frame export to make background transparency \ No newline at end of file +config.description.lastExportTransparentBackground = Last setting of ignoring background color for frame export to make background transparency + +config.name.warningAbcOptimize = Warn on Abc optimize action +config.description.warningAbcOptimize = Show warning before doing Abc optimize action + diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties index c46d221d3..c5af4db43 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties @@ -750,3 +750,15 @@ config.description.jnaTempDirectory = Cesta k do\u010dasn\u00e9mu adres\u00e1\u0 config.name.flaExportFixShapes = FLA export - opravovat tvary (pomal\u00e9) config.description.flaExportFixShapes = Aplikovat proceduru rozd\u011blov\u00e1n\u00ed p\u0159ekr\u00fdvaj\u00edc\u00edch se hran pro opravu chyb\u011bj\u00edc\u00edch v\u00fdpln\u00ed n\u011bkter\u00fdch druh\u016f tvar\u016f. Tohle m\u016f\u017ee b\u00fdt velmi pomal\u00e9 na n\u011bkter\u00fdch slo\u017eit\u011bj\u0161\u00edch tvarech. + +config.name.lastExportResampleWav = Posledn\u00ed nastaven\u00ed p\u0159evzorkov\u00e1n\u00ed wavu +config.description.lastExportResampleWav = Posledn\u00ed nastaven\u00ed p\u0159evzorkov\u00e1n\u00ed wavu na 44kHz + +config.name.previewResampleSound = P\u0159evzorkovat v n\u00e1hledech zvuku +config.description.previewResampleSound = P\u0159evzorkovat na 44kHz v n\u00e1hledech zvuku + +config.name.lastExportTransparentBackground = Posledn\u00ed nastaven\u00ed ignorov\u00e1n\u00ed barvy pozad\u00ed v exportu sn\u00edmk\u016f +config.description.lastExportTransparentBackground = Posledn\u00ed nastaven\u00ed ignorov\u00e1n\u00ed barvy pozad\u00ed v exportu sn\u00edmk\u016f pro pou\u017eit\u00ed pr\u016fhledn\u00e9ho pozad\u00ed + +config.name.warningAbcOptimize = Varovat p\u0159i Abc optimizaci +config.description.warningAbcOptimize = Zobrazovat varov\u00e1n\u00ed p\u0159ed proveden\u00edm Abc optimizace \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/abc/ABCExplorerDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/abc/ABCExplorerDialog.properties index 5e56866d1..10eca91a0 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/abc/ABCExplorerDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/abc/ABCExplorerDialog.properties @@ -37,4 +37,10 @@ copy.paths.all = Copy all paths to clipboard hilight.usage = Hilight selected path goto.path = Go to path -goto.path.label = Enter path to navigate to \ No newline at end of file +goto.path.label = Enter path to navigate to + +button.optimize = Optimize - remove unused items + +warning.optimize = This action will remove items from ABC which have zero usages - they are not referenced form any script.\r\n\ + Some kinds of obfuscated SWFs could be damaged this way.\r\n\ + Use it at your own risk. Do you want to continue? \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/abc/ABCExplorerDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/abc/ABCExplorerDialog_cs.properties index 85b87b75b..b482fd3bd 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/abc/ABCExplorerDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/abc/ABCExplorerDialog_cs.properties @@ -38,4 +38,10 @@ copy.paths.all = Kop\u00edrovat v\u0161echny cesty do schr\u00e1nky hilight.usage = Zv\u00fdraznit vybranou cestu goto.path = P\u0159ej\u00edt na cestu -goto.path.label = Zadejte cestu kam p\u0159ej\u00edt \ No newline at end of file +goto.path.label = Zadejte cestu kam p\u0159ej\u00edt + +button.optimize = Optimalizovat - odebrat nepou\u017e\u00edvan\u00e9 polo\u017eky + +warning.optimize = Tato akce odstran\u00ed z ABC polo\u017eky, kter\u00e9 maj\u00ed nula pou\u017eit\u00ed - nen\u00ed na n\u011b odkazov\u00e1no z \u017e\u00e1dn\u00e9ho skriptu.\r\n\ + N\u011bkter\u00e9 druhy obfuskovan\u00fdch SWF to m\u016f\u017ee po\u0161kodit.\r\n\ + Pou\u017e\u00edvejte ji na vlastn\u00ed riziko. Chcete pokra\u010dovat? \ No newline at end of file