diff --git a/CHANGELOG.md b/CHANGELOG.md index 90209810d..c39c743ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ All notable changes to this project will be documented in this file. - AS1/2: displaying script path in the error log when jump to invalid address - AS1/2: Try..catch with Error types - decompilation and direct editation - AS1/2: Properly handling of Flash7 scripts exported to Flash5/6 - ActionDefineFunction local registers +- Export AS1/2 P-code as GraphViz +- Display better Graph using GraphViz (Must be configured in Advanced settings / Path) +- Copy AS1/2 Graph source (GraphViz) to clipboard - rightclick menu on graph ### Changed - AS1/2 direct editation - generated constantpool is sorted according to ActionPush position diff --git a/antlib/jacocoant.jar b/antlib/jacocoant.jar index 43340ba1c..09d4a4796 100644 Binary files a/antlib/jacocoant.jar and b/antlib/jacocoant.jar differ diff --git a/lib/graphs.jar b/lib/graphs.jar new file mode 100644 index 000000000..8d3a86ff0 Binary files /dev/null and b/lib/graphs.jar differ diff --git a/libsrc/ffdec_lib/antlib/jacocoant.jar b/libsrc/ffdec_lib/antlib/jacocoant.jar index 43340ba1c..09d4a4796 100644 Binary files a/libsrc/ffdec_lib/antlib/jacocoant.jar and b/libsrc/ffdec_lib/antlib/jacocoant.jar differ diff --git a/libsrc/ffdec_lib/nbproject/project.xml b/libsrc/ffdec_lib/nbproject/project.xml index a8c93a845..01bd7c25e 100644 --- a/libsrc/ffdec_lib/nbproject/project.xml +++ b/libsrc/ffdec_lib/nbproject/project.xml @@ -239,7 +239,7 @@ auxiliary.show.customizer.message= src - ../../lib/LZMA.jar;../../lib/avi.jar;../../lib/gif.jar;../../lib/gnujpdf.jar;../../lib/jl1.0.1.jar;../../lib/jpacker.jar;../../lib/nellymoser.jar;../../lib/sfntly.jar;../../lib/ttf.jar;../../lib/cmykjpeg.jar;../../src;../../lib/flashdebugger.jar + ../../lib/LZMA.jar;../../lib/avi.jar;../../lib/gif.jar;../../lib/gnujpdf.jar;../../lib/jl1.0.1.jar;../../lib/jpacker.jar;../../lib/nellymoser.jar;../../lib/sfntly.jar;../../lib/ttf.jar;../../lib/cmykjpeg.jar;../../src;../../lib/flashdebugger.jar;../../lib/graphs.jar build reports dist diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java index 8d0499b23..0288af0fe 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java @@ -303,6 +303,16 @@ public class AVM2DeobfuscatorRegisters extends AVM2DeobfuscatorSimple { public long pos2adr(int pos) { return code.pos2adr(pos); } + + @Override + public Set getImportantAddresses() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String insToString(int pos) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } }); idx = branches.get(0); for (int n = 1; n < branches.size(); n++) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegistersOld.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegistersOld.java index a53ab1816..d0e6392a6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegistersOld.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegistersOld.java @@ -266,6 +266,16 @@ public class AVM2DeobfuscatorRegistersOld extends AVM2DeobfuscatorSimpleOld { public long pos2adr(int pos) { return code.pos2adr(pos); } + + @Override + public Set getImportantAddresses() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String insToString(int pos) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } }); idx = branches.get(0); for (int n = 1; n < branches.size(); n++) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2GraphSource.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2GraphSource.java index a6bb038d5..7d8d50788 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2GraphSource.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2GraphSource.java @@ -24,6 +24,7 @@ import com.jpexs.decompiler.flash.abc.avm2.ConvertOutput; import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; import com.jpexs.helpers.Reference; import com.jpexs.decompiler.flash.abc.types.MethodBody; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.decompiler.graph.GraphPart; import com.jpexs.decompiler.graph.GraphSource; @@ -34,6 +35,7 @@ import com.jpexs.decompiler.graph.TranslateStack; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Set; /** * @@ -85,6 +87,19 @@ public class AVM2GraphSource extends GraphSource { code.calculateDebugFileLine(abc); } + @Override + public Set getImportantAddresses() { + return code.getImportantOffsets(body, false); + } + + @Override + public String insToString(int pos) { + if (pos < code.code.size()) { + return code.code.get(pos).toStringNoAddress(abc.constants, fullyQualifiedNames); + } + return ""; + } + @Override public int size() { return code.code.size(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/AVM2Instruction.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/AVM2Instruction.java index 63cc0b5dd..f1420e948 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/AVM2Instruction.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/AVM2Instruction.java @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.abc.avm2.instructions; import com.jpexs.decompiler.flash.BaseLocalData; @@ -123,6 +124,7 @@ public class AVM2Instruction implements Cloneable, GraphSourceItem { return bos.toByteArray(); } + @Override public int getBytesLength() { int cnt = 1; for (int i = 0; i < definition.operands.length; i++) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java index d52275288..c69e7894c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java @@ -347,6 +347,7 @@ public abstract class Action implements GraphSourceItem { * * @return Length */ + @Override public final int getBytesLength() { return getContentBytesLength() + (actionCode >= 0x80 ? 3 : 1); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java index 1ce070baa..2a5aa4585 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java @@ -18,6 +18,8 @@ package com.jpexs.decompiler.flash.action; import com.jpexs.decompiler.flash.BaseLocalData; import com.jpexs.decompiler.flash.FinalProcessLocalData; +import com.jpexs.decompiler.flash.SWF; +import static com.jpexs.decompiler.flash.action.Action.adr2ip; import com.jpexs.decompiler.flash.action.model.DirectValueActionItem; import com.jpexs.decompiler.flash.action.model.EnumerateActionItem; import com.jpexs.decompiler.flash.action.model.FunctionActionItem; @@ -35,14 +37,17 @@ import com.jpexs.decompiler.flash.action.swf4.ActionJump; import com.jpexs.decompiler.flash.action.swf4.ActionNot; import com.jpexs.decompiler.flash.action.swf4.ActionPush; import com.jpexs.decompiler.flash.action.swf4.RegisterNumber; +import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction; import com.jpexs.decompiler.flash.action.swf5.ActionEquals2; import com.jpexs.decompiler.flash.action.swf5.ActionStoreRegister; import com.jpexs.decompiler.flash.action.swf6.ActionStrictEquals; +import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2; import com.jpexs.decompiler.flash.ecma.Null; import com.jpexs.decompiler.graph.Graph; import com.jpexs.decompiler.graph.GraphPart; import com.jpexs.decompiler.graph.GraphSource; import com.jpexs.decompiler.graph.GraphSourceItem; +import com.jpexs.decompiler.graph.GraphSourceItemContainer; import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.decompiler.graph.Loop; import com.jpexs.decompiler.graph.TranslateStack; @@ -52,8 +57,10 @@ import com.jpexs.decompiler.graph.model.DefaultItem; import com.jpexs.decompiler.graph.model.IfItem; import com.jpexs.decompiler.graph.model.SwitchItem; import com.jpexs.decompiler.graph.model.WhileItem; +import com.jpexs.helpers.Helper; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -71,6 +78,37 @@ public class ActionGraph extends Graph { this.insideDoInitAction = insideDoInitAction; } + @Override + public LinkedHashMap getSubGraphs() { + LinkedHashMap subgraphs = new LinkedHashMap<>(); + List alist = ((ActionGraphSource) code).getActions(); + int ip = 0; + for (Action action : alist) { + if ((action instanceof GraphSourceItemContainer) && ((action instanceof ActionDefineFunction) || (action instanceof ActionDefineFunction2))) { + GraphSourceItemContainer cnt = (GraphSourceItemContainer) action; + String functionName = (action instanceof ActionDefineFunction) ? ((ActionDefineFunction) action).functionName : ((ActionDefineFunction2) action).functionName; + long endAddr = action.getAddress() + cnt.getHeaderSize(); + List outs = new ArrayList<>(); + for (long size : cnt.getContainerSizes()) { + if (size == 0) { + outs.add(new ActionList()); + continue; + } + outs.add(new ActionList(alist.subList(adr2ip(alist, endAddr), adr2ip(alist, endAddr + size)))); + endAddr += size; + } + + for (ActionList al : outs) { + subgraphs.put("loc" + Helper.formatAddress(code.pos2adr(ip)) + ": function " + functionName, + new ActionGraph("", false, al, new HashMap<>(), new HashMap<>(), new HashMap<>(), SWF.DEFAULT_VERSION) + ); + } + } + ip++; + } + return subgraphs; + } + public boolean isInsideDoInitAction() { return insideDoInitAction; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java index e1458a84b..fcb5f8c4a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java @@ -17,6 +17,8 @@ package com.jpexs.decompiler.flash.action; import com.jpexs.decompiler.flash.BaseLocalData; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; import com.jpexs.helpers.Reference; import com.jpexs.decompiler.graph.GraphPart; import com.jpexs.decompiler.graph.GraphSource; @@ -27,6 +29,7 @@ import com.jpexs.helpers.Helper; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -36,7 +39,7 @@ import java.util.logging.Logger; */ public class ActionGraphSource extends GraphSource { - private final List actions; + private final ActionList actions; public int version; @@ -55,7 +58,7 @@ public class ActionGraphSource extends GraphSource { } public ActionGraphSource(String path, boolean insideDoInitAction, List actions, int version, HashMap registerNames, HashMap variables, HashMap functions) { - this.actions = actions; + this.actions = actions instanceof ActionList ? (ActionList) actions : new ActionList(actions); this.version = version; this.registerNames = registerNames; this.variables = variables; @@ -64,6 +67,19 @@ public class ActionGraphSource extends GraphSource { this.path = path; } + @Override + public Set getImportantAddresses() { + return Action.getActionsAllRefs(actions); + } + + @Override + public String insToString(int pos) { + if (pos < actions.size()) { + return actions.get(pos).getASMSource(actions, getImportantAddresses(), ScriptExportMode.PCODE); + } + return ""; + } + @Override public int size() { return actions.size(); 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 eaa8a774c..25996002f 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 @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.configuration; import com.jpexs.decompiler.flash.ApplicationInfo; @@ -631,6 +632,11 @@ public final class Configuration { @ConfigurationCategory("ui") public static final ConfigurationItem showSetAdvanceValuesMessage = null; + @ConfigurationDefaultString("") + @ConfigurationCategory("paths") + @ConfigurationFile + public static final ConfigurationItem graphVizDotLocation = null; + private enum OSId { WINDOWS, OSX, UNIX } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/modes/ScriptExportMode.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/modes/ScriptExportMode.java index a759c4c20..c6b74519d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/modes/ScriptExportMode.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/modes/ScriptExportMode.java @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.exporters.modes; /** @@ -23,7 +24,8 @@ public enum ScriptExportMode { AS, PCODE, PCODE_HEX, + PCODE_GRAPHVIZ, HEX, CONSTANTS, // AS1/2 ConstantPool - AS_METHOD_STUBS; + AS_METHOD_STUBS } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/ExportScriptTask.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/ExportScriptTask.java index b91ee5bca..d8c46a514 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/ExportScriptTask.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/ExportScriptTask.java @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.exporters.script; import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler; @@ -98,6 +99,8 @@ public class ExportScriptTask implements Callable { asm.getActionSourcePrefix(writer2); asm.getActionBytesAsHex(writer2); asm.getActionSourceSuffix(writer2); + } else if (exportMode == ScriptExportMode.PCODE_GRAPHVIZ) { + new PcodeGraphVizExporter().exportAs12(asm, writer2); } else if (exportMode != ScriptExportMode.AS) { asm.getActionSourcePrefix(writer2); asm.getASMSource(exportMode, writer2, null); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/PcodeGraphVizExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/PcodeGraphVizExporter.java new file mode 100644 index 000000000..1d575dd8a --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/PcodeGraphVizExporter.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2010-2018 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.exporters.script; + +import com.jpexs.decompiler.flash.AppResources; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.graph.AVM2Graph; +import com.jpexs.decompiler.flash.abc.types.MethodBody; +import com.jpexs.decompiler.flash.action.Action; +import static com.jpexs.decompiler.flash.action.Action.adr2ip; +import com.jpexs.decompiler.flash.action.ActionGraph; +import com.jpexs.decompiler.flash.action.ActionGraphSource; +import com.jpexs.decompiler.flash.action.ActionList; +import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction; +import com.jpexs.decompiler.flash.action.swf5.ActionWith; +import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2; +import com.jpexs.decompiler.flash.action.swf7.ActionTry; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.helpers.StringBuilderTextWriter; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.graph.Graph; +import com.jpexs.decompiler.graph.GraphPart; +import com.jpexs.decompiler.graph.GraphSource; +import com.jpexs.decompiler.graph.GraphSourceItemContainer; +import com.jpexs.decompiler.graph.GraphTargetItem; +import com.jpexs.decompiler.graph.ScopeStack; +import com.jpexs.decompiler.graph.TranslateException; +import com.jpexs.decompiler.graph.model.CommentItem; +import com.jpexs.graphs.graphviz.dot.parser.DotId; +import com.jpexs.helpers.Helper; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +/** + * + * @author JPEXS + */ +public class PcodeGraphVizExporter { + + private final String BLOCK_STYLE = "shape=\"box\""; + + private static final int INS_LEN_LIMIT = 50; + private static final String ELIPSIS = "..."; + + private String getBlockName(GraphSource list, GraphPart part) { + return "loc" + Helper.formatAddress(list.pos2adr(part.start, true)); + } + + private boolean isEndOfScript(GraphSource list, GraphPart part) { + if (part.start >= list.size()) { + return true; + } + return false; + } + + private static void populateParts(GraphPart part, Set allParts) { + if (allParts.contains(part)) { + return; + } + allParts.add(part); + for (GraphPart p : part.nextParts) { + populateParts(p, allParts); + } + } + + public void exportAs12(ASMSource src, GraphTextWriter writer) throws InterruptedException { + ActionList alist = src.getActions(); + ActionGraph gr = new ActionGraph("", false, alist, new HashMap<>(), new HashMap<>(), new HashMap<>(), SWF.DEFAULT_VERSION); + export(gr, writer); + } + + public void exportAs3(ABC abc, MethodBody body, GraphTextWriter writer) throws InterruptedException { + AVM2Graph gr = new AVM2Graph(body.getCode(), abc, body, false, -1, -1, new HashMap<>(), new ScopeStack(), new HashMap<>(), new ArrayList<>(), new HashMap<>(), body.getCode().visitCode(body)); + export(gr, writer); + } + + private void exportGraph(Graph graph, GraphTextWriter writer) throws InterruptedException { + graph.init(null); + GraphSource graphSource = graph.getGraphCode(); + Set allBlocks = new HashSet<>(); + List heads = graph.heads; + for (GraphPart h : heads) { + populateParts(h, allBlocks); + } + + Set knownAddresses = graphSource.getImportantAddresses(); + int h = 0; + for (GraphPart head : heads) { + String headName = "start"; + if (heads.size() > 1) { + h++; + headName = "start" + h; + } + writer.append(headName + " [shape=\"circle\"]\r\n"); + writer.append(headName + ":s -> " + getBlockName(graphSource, head) + ":n;\r\n"); + } + for (GraphPart part : allBlocks) { + StringBuilder blkCodeBuilder = new StringBuilder(); + for (int j = part.start; j <= part.end; j++) { + if (j < graphSource.size()) { + if (knownAddresses.contains(graphSource.get(j).getAddress())) { + blkCodeBuilder.append("loc").append(Helper.formatAddress(graphSource.get(j).getAddress())).append(":\r\n"); + } + String insStr = graphSource.insToString(j); + if (insStr.length() > INS_LEN_LIMIT) { + insStr = insStr.substring(0, INS_LEN_LIMIT - ELIPSIS.length()) + ELIPSIS; + } + blkCodeBuilder.append(insStr).append("\r\n"); + } + } + String labelStr = blkCodeBuilder.toString(); + labelStr = labelStr.replace("\"", "\\\""); + labelStr = labelStr.replace("\r\n", "\\l"); + String partBlockName = getBlockName(graphSource, part); + String blkStyle = BLOCK_STYLE; + if (isEndOfScript(graphSource, part)) { + blkStyle = "shape=\"circle\""; + labelStr = "FINISH"; + } + writer.append(partBlockName + " [" + blkStyle + " label=\"" + labelStr + "\"];\r\n"); + for (int n = 0; n < part.nextParts.size(); n++) { + GraphPart next = part.nextParts.get(n); + String orientation = ":s"; + if (part.nextParts.size() == 2 && n == 0) { + orientation = ""; + } + if (part.nextParts.size() == 2 && n == 1) { + orientation = ""; + } + String nextBlockName = getBlockName(graphSource, next); + writer.append(partBlockName + orientation + " -> " + nextBlockName + ":n;\r\n"); + } + } + } + + public void export(Graph graph, GraphTextWriter writer) throws InterruptedException { + writer.append("digraph pcode {\r\n"); + exportGraph(graph, writer); + int pos = 0; + Map subgraphs = graph.getSubGraphs(); + for (String name : subgraphs.keySet()) { + writer.append("subgraph cluster_" + pos + " {"); + writer.append("label=" + new DotId(name, false) + ";\r\n"); + pos++; + exportGraph(subgraphs.get(name), writer); + writer.append("}"); + } + writer.append("}\r\n"); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/settings/ScriptExportSettings.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/settings/ScriptExportSettings.java index 7a6ef81c1..ee2b48256 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/settings/ScriptExportSettings.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/settings/ScriptExportSettings.java @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.exporters.settings; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; @@ -42,6 +43,8 @@ public class ScriptExportSettings { case AS: case AS_METHOD_STUBS: return ".as"; + case PCODE_GRAPHVIZ: + return ".gv"; case PCODE: case PCODE_HEX: return ".pcode"; 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 6e445b030..3304fe011 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -20,6 +20,8 @@ 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.action.swf5.ActionDefineFunction; +import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2; import com.jpexs.decompiler.flash.helpers.GraphTextWriter; import com.jpexs.decompiler.graph.model.AndItem; import com.jpexs.decompiler.graph.model.BreakItem; @@ -51,6 +53,7 @@ import com.jpexs.decompiler.graph.model.WhileItem; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -80,6 +83,14 @@ public class Graph { private boolean debugGetLoops = false; private boolean debugPrintGraph = false; + public GraphSource getGraphCode() { + return code; + } + + public LinkedHashMap getSubGraphs() { + return new LinkedHashMap<>(); + } + /** * Identify loop exits * @@ -1141,6 +1152,7 @@ public class Graph { } if (stopPart.contains(part)) { + //System.err.println("/stopped part " + part); return; } for (Loop el : loops) { @@ -1163,9 +1175,11 @@ public class Graph { if (visited.contains(part)) { part.level = 0; + //System.err.println("set level " + part + " to zero"); } else { visited.add(part); part.level = level; + //System.err.println("set level " + part + " to " + level); } boolean isLoop = false; @@ -1183,9 +1197,18 @@ public class Graph { if (nextParts == null) { nextParts = part.nextParts; } + nextParts = new ArrayList<>(nextParts); + + if (nextParts.size() == 2 && stopPart.contains(nextParts.get(1))) { + nextParts.remove(1); + } + if (nextParts.size() >= 1 && stopPart.contains(nextParts.get(0))) { + nextParts.remove(0); + } if (nextParts.size() == 2) { GraphPart next = getCommonPart(localData, nextParts, loops);//part.getNextPartPath(new ArrayList()); + //System.err.println("- common part of " + nextParts.get(0) + " and " + nextParts.get(1) + " is " + next); List stopParts2 = new ArrayList<>(); //stopPart); if (next != null) { stopParts2.add(next); @@ -1193,12 +1216,20 @@ public class Graph { stopParts2.add(stopPart.get(stopPart.size() - 1)); } if (next != nextParts.get(0)) { + // System.err.println("- going to branch 0 nextpart from " + part + " to " + nextParts.get(0)); markLevels(path, localData, nextParts.get(0), allParts, loops, next == null ? stopPart : stopParts2, level + 1, visited, recursionLevel + 1); + } else { + //System.err.println("- branch 0 of " + part + " is skipped (=next)"); } + if (next != nextParts.get(1)) { + //System.err.println("- going to branch 1 nextpart from " + part + " to " + nextParts.get(1)); markLevels(path, localData, nextParts.get(1), allParts, loops, next == null ? stopPart : stopParts2, level + 1, visited, recursionLevel + 1); + } else { + //System.err.println("- branch 1 of " + part + " is skipped (=next)"); } if (next != null) { + //System.err.println("- going to next from " + part + " to " + next); markLevels(path, localData, next, allParts, loops, stopPart, level, visited, recursionLevel + 1); } } @@ -1235,6 +1266,7 @@ public class Graph { } if (nextParts.size() == 1) { + //System.err.println("going to one nexpart from " + part + " to " + nextParts.get(0)); markLevels(path, localData, nextParts.get(0), allParts, loops, stopPart, level, visited, recursionLevel + 1); } @@ -1258,6 +1290,7 @@ public class Graph { if (isLoop) { if (currentLoop != null && currentLoop.loopBreak != null) { currentLoop.phase = 2; + //System.err.println("- going to break of loop " + currentLoop.loopBreak); markLevels(path, localData, currentLoop.loopBreak, allParts, loops, stopPart, level, visited, recursionLevel + 1); } } @@ -2179,13 +2212,6 @@ public class Graph { 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); @@ -2267,7 +2293,7 @@ public class Graph { protected void checkGraph(List allBlocks) { } - private List makeGraph(GraphSource code, List allBlocks, List alternateEntries) throws InterruptedException { + public 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()]; @@ -2344,7 +2370,18 @@ public class Graph { endAddr += size; } ip = code.adr2pos(endAddr); + + if ((ins instanceof ActionDefineFunction) || (ins instanceof ActionDefineFunction2)) { + part.end = lastIp; + allBlocks.add(part); + GraphPart gp = new GraphPart(ip, -1); + gp.path = path; + part.nextParts.add(gp); + gp.refs.add(part); + part = gp; + } } + continue; } else if (ins.isExit()) { part.end = ip; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSource.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSource.java index 3bc7e66e3..7c01967b8 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSource.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSource.java @@ -18,10 +18,12 @@ package com.jpexs.decompiler.graph; import com.jpexs.decompiler.flash.BaseLocalData; import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Set; /** * @@ -37,6 +39,10 @@ public abstract class GraphSource implements Serializable { public abstract List translatePart(GraphPart part, BaseLocalData localData, TranslateStack stack, int start, int end, int staticOperation, String path) throws InterruptedException; + public abstract Set getImportantAddresses(); + + public abstract String insToString(int pos); + private void visitCode(int ip, int lastIp, HashMap> refs, int endIp) throws InterruptedException { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); @@ -109,4 +115,20 @@ public abstract class GraphSource implements Serializable { public abstract int adr2pos(long adr); public abstract long pos2adr(int pos); + + public long getAddressAfterCode() { + if (isEmpty()) { + return 0; + } + long lastAddr = pos2adr(size() - 1); + return lastAddr + get(size() - 1).getBytesLength(); + } + + public final long pos2adr(int pos, boolean allowPosAfterCode) { + if (pos == size() && allowPosAfterCode) { + return getAddressAfterCode(); + } + return pos2adr(pos); + } +; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSourceItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSourceItem.java index 5c1eb3a97..66bf74294 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSourceItem.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSourceItem.java @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.graph; import com.jpexs.decompiler.flash.BaseLocalData; @@ -56,4 +57,6 @@ public interface GraphSourceItem extends Serializable, Cloneable { public int getLine(); public String getFile(); + + public abstract int getBytesLength(); } diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java index 700e78f3f..89273b64a 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java @@ -1948,4 +1948,29 @@ public class ActionScript2Test extends ActionScript2TestBase { + "trace(\"after\");\r\n" ); } + + @Test + public void frame70_forWithContinue2Test() { + compareSrc(70, "trace(\"forWithContinue2Test\");\r\n" + + "var s = \"A\";\r\n" + + "var i = 0;\r\n" + + "for(; i < 10; i++)\r\n" + + "{\r\n" + + "if(s == \"B\")\r\n" + + "{\r\n" + + "if(s == \"C\")\r\n" + + "{\r\n" + + "continue;\r\n" + + "}\r\n" + + "}\r\n" + + "trace(\"D\");\r\n" + + "var j = 0;\r\n" + + "while(j < 29)\r\n" + + "{\r\n" + + "trace(\"E\");\r\n" + + "j++;\r\n" + + "}\r\n" + + "}\r\n" + ); + } } diff --git a/libsrc/ffdec_lib/testdata/as2/as2.fla b/libsrc/ffdec_lib/testdata/as2/as2.fla index 4b38fcd38..47ee76926 100644 Binary files a/libsrc/ffdec_lib/testdata/as2/as2.fla and b/libsrc/ffdec_lib/testdata/as2/as2.fla differ diff --git a/libsrc/ffdec_lib/testdata/as2/as2.swf b/libsrc/ffdec_lib/testdata/as2/as2.swf index 3b1088827..6630bec7e 100644 Binary files a/libsrc/ffdec_lib/testdata/as2/as2.swf and b/libsrc/ffdec_lib/testdata/as2/as2.swf differ diff --git a/nbproject/project.xml b/nbproject/project.xml index 8e37dff20..405584824 100644 --- a/nbproject/project.xml +++ b/nbproject/project.xml @@ -323,7 +323,7 @@ src - lib/LZMA.jar;lib/jna-3.5.1.jar;lib/jpproxy.jar;lib/trident-6.2.jar;lib/substance-flamingo-6.2.jar;lib/flamingo-6.2.jar;lib/substance-6.2.jar;lib/jl1.0.1.jar;lib/nellymoser.jar;lib/gif.jar;lib/avi.jar;lib/ttf.jar;lib/jpacker.jar;lib/sfntly.jar;lib/gnujpdf.jar;libsrc/ffdec_lib/src;lib/tablelayout.jar;lib/jsyntaxpane-0.9.5.jar;lib/JavactiveX.jar;lib/flashdebugger.jar;lib/treetable.jar;lib/minimal-json-0.9.5.jar + lib/LZMA.jar;lib/jna-3.5.1.jar;lib/jpproxy.jar;lib/trident-6.2.jar;lib/substance-flamingo-6.2.jar;lib/flamingo-6.2.jar;lib/substance-6.2.jar;lib/jl1.0.1.jar;lib/nellymoser.jar;lib/gif.jar;lib/avi.jar;lib/ttf.jar;lib/jpacker.jar;lib/sfntly.jar;lib/gnujpdf.jar;libsrc/ffdec_lib/src;lib/tablelayout.jar;lib/jsyntaxpane-0.9.5.jar;lib/JavactiveX.jar;lib/flashdebugger.jar;lib/treetable.jar;lib/minimal-json-0.9.5.jar;lib/graphs.jar build javadoc reports diff --git a/src/com/jpexs/decompiler/flash/gui/GraphDialog.java b/src/com/jpexs/decompiler/flash/gui/GraphDialog.java index 525fadf74..386dfb59d 100644 --- a/src/com/jpexs/decompiler/flash/gui/GraphDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/GraphDialog.java @@ -16,24 +16,40 @@ */ package com.jpexs.decompiler.flash.gui; +import com.jpexs.decompiler.flash.exporters.script.PcodeGraphVizExporter; +import com.jpexs.decompiler.flash.gui.AppDialog; +import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.gui.graph.AbstractGraphPanel; +import com.jpexs.decompiler.flash.gui.graph.GraphPanelSimple; +import com.jpexs.decompiler.flash.gui.graph.GraphVizGraphPanel; +import com.jpexs.decompiler.flash.helpers.StringBuilderTextWriter; import com.jpexs.decompiler.graph.Graph; -import com.jpexs.decompiler.graph.GraphPart; import java.awt.BorderLayout; -import java.awt.Color; import java.awt.Container; +import java.awt.Cursor; import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Graphics2D; +import java.awt.FlowLayout; +import java.awt.Font; import java.awt.Point; -import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.Toolkit; import java.awt.Window; -import java.awt.geom.AffineTransform; -import java.awt.geom.Line2D; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JLabel; +import javax.swing.JMenuItem; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JScrollPane; +import javax.swing.JViewport; +import javax.swing.SwingUtilities; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; /** * @@ -41,217 +57,7 @@ import javax.swing.JScrollPane; */ public class GraphDialog extends AppDialog { - private class GraphPanel extends JPanel { - - private static final int SPACE_VERTICAL = 16; - - private static final int SPACE_HORIZONTAL = 10; - - private static final int SPACE_BACKLINKS = 5; - - private static final int BLOCK_WIDTH = 200; - - private static final int BLOCK_HEIGHT = 20; - - private final HashMap partPos = new HashMap<>(); - - private final Point size; - - private int backLinksLeft = 0; - - private int backLinksRight = 0; - - private final GraphPart head; - - public GraphPanel(Graph graph) throws InterruptedException { - graph.init(null); - size = getPartPositions(head = graph.heads.get(0), SPACE_VERTICAL + SPACE_VERTICAL + BLOCK_HEIGHT / 2, getPartWidth(graph.heads.get(0), new HashSet<>()) * (BLOCK_WIDTH + SPACE_HORIZONTAL) / 2 - SPACE_HORIZONTAL, partPos, true); - backLinksLeft = 1; - backLinksRight = 1; - for (GraphPart part : partPos.keySet()) { - Point p = partPos.get(part); - for (GraphPart n : part.nextParts) { - Point npos = partPos.get(n); - if (npos.y < p.y) { - if (p.x > size.x / 2) { - backLinksRight++; - } else { - backLinksLeft++; - } - } - } - } - size.x += 2 * SPACE_BACKLINKS + backLinksLeft * SPACE_BACKLINKS + backLinksRight * SPACE_BACKLINKS; - setPreferredSize(new Dimension(size.x, size.y)); - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - Graphics2D g2 = (Graphics2D) g; - /*g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON);*/ - g2.setColor(Color.black); - int startX = SPACE_BACKLINKS + backLinksLeft * SPACE_BACKLINKS; - int blLeft = 0; - int blRight = 0; - for (GraphPart part : partPos.keySet()) { - Point p = partPos.get(part); - g2.setColor(Color.white); - g2.fillRect(startX + p.x - BLOCK_WIDTH / 2, p.y - BLOCK_HEIGHT / 2, BLOCK_WIDTH, BLOCK_HEIGHT); - g2.setColor(Color.black); - g2.drawRect(startX + p.x - BLOCK_WIDTH / 2, p.y - BLOCK_HEIGHT / 2, BLOCK_WIDTH, BLOCK_HEIGHT); - g2.drawString(part.toString(), startX + p.x - g2.getFontMetrics().stringWidth(part.toString()) / 2, p.y + g2.getFontMetrics().getHeight() / 2); - } - - Point hp = partPos.get(head); - drawArrow(g2, startX + hp.x, hp.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL, startX + hp.x, hp.y - BLOCK_HEIGHT / 2); - - for (GraphPart part : partPos.keySet()) { - Point p = partPos.get(part); - - for (int n = 0; n < part.nextParts.size(); n++) { - boolean isIf = part.nextParts.size() == 2; - if (isIf) { - if (n == 0) { - g2.setColor(new Color(0, 0x80, 0)); - } else { - g2.setColor(Color.red); - } - } else { - g2.setColor(Color.black); - } - Point npos = partPos.get(part.nextParts.get(n)); - if (npos.y < p.y) { - int sidex = startX; - if (p.x > size.x / 2) { - blRight++; - sidex = size.x - backLinksRight * SPACE_BACKLINKS; - sidex += SPACE_BACKLINKS + SPACE_BACKLINKS * blRight; - } else { - blLeft++; - sidex -= SPACE_BACKLINKS + SPACE_BACKLINKS * blLeft; - } - g2.drawLine(startX + p.x, p.y + BLOCK_HEIGHT / 2, startX + p.x, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); - g2.drawLine(startX + p.x, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2, sidex, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); - g2.drawLine(sidex, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, sidex, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); - drawArrow(g2, sidex, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2); - g2.drawLine(startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2); - } else { - drawArrow(g2, startX + p.x, p.y + BLOCK_HEIGHT / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2); - g2.drawLine(startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2); - } - } - } - } - - private Point getPartSubPositions(Point ret, int totalWidth, GraphPart part, int y, int x, HashMap used) { - if (used.containsKey(part)) { - Point p = used.get(part); - return new Point(x, y); - } - used.put(part, new Point(x, y)); - if (part.nextParts.size() > 0) { - int cx = x - totalWidth / 2; - for (int p = 0; p < part.nextParts.size(); p++) { - HashSet k = new HashSet<>(); - k.addAll(used.keySet()); - int partWidth = getPartWidth(part.nextParts.get(p), k); - int cellWidth = partWidth * (BLOCK_WIDTH + SPACE_HORIZONTAL); - Point pt = getPartPositions(part.nextParts.get(p), y + BLOCK_HEIGHT + SPACE_VERTICAL, cx + cellWidth / 2, used, false); - if (pt.x > ret.x) { - ret.x = pt.x; - } - if (pt.y > ret.y) { - ret.y = pt.y; - } - cx += cellWidth; - - } - cx = x - totalWidth / 2; - for (int p = 0; p < part.nextParts.size(); p++) { - HashSet k = new HashSet<>(); - k.addAll(used.keySet()); - int cellWidth = getPartWidth(part.nextParts.get(p), k) * (BLOCK_WIDTH + SPACE_HORIZONTAL); - - HashSet hs = new HashSet<>(); - hs.addAll(used.keySet()); - int totalWidthParts2 = getPartWidth(part.nextParts.get(p), hs); - int totalWidth2 = totalWidthParts2 * (BLOCK_WIDTH + SPACE_HORIZONTAL); - - Point pt = getPartSubPositions(ret, totalWidth2, part.nextParts.get(p), y + BLOCK_HEIGHT + SPACE_VERTICAL, cx + cellWidth / 2, used); - if (pt.x > ret.x) { - ret.x = pt.x; - } - if (pt.y > ret.y) { - ret.y = pt.y; - } - cx += cellWidth; - } - - } - return ret; - } - - private Point getPartPositions(GraphPart part, int y, int x, HashMap used, boolean goSub) { - HashMap l = new HashMap<>(); - l.putAll(used); - HashSet hs = new HashSet<>(); - hs.addAll(l.keySet()); - int totalWidthParts = getPartWidth(part, hs); - int totalWidth = totalWidthParts * (BLOCK_WIDTH + SPACE_HORIZONTAL); - - if (used.containsKey(part)) { - Point p = used.get(part); - return new Point(x, y); - } - Point ret = new Point(x - BLOCK_WIDTH / 2 - SPACE_HORIZONTAL / 2 + BLOCK_WIDTH, y + BLOCK_HEIGHT); - if (goSub) { - Point p2 = getPartSubPositions(ret, totalWidth, part, y, x, used); - if (p2.x > ret.x) { - ret.x = p2.x; - } - if (p2.y > ret.y) { - ret.y = p2.y; - } - } - return ret; - } - - private int getPartHeight(GraphPart part, List used) { - if (used.contains(part)) { - return 1; - } - used.add(part); - int maxH = 0; - for (int p = 0; p < part.nextParts.size(); p++) { - int h = getPartHeight(part.nextParts.get(p), used); - if (h > maxH) { - maxH = h; - } - } - return 1 + maxH; - } - - private int getPartWidth(GraphPart part, HashSet used) { - - if (used.contains(part)) { - return 1; - } - used.add(part); - if (part.nextParts.isEmpty()) { - return 1; - } - int w = 0; - for (GraphPart subpart : part.nextParts) { - w += getPartWidth(subpart, used); - } - return w; - } - } - - GraphPanel gp; + AbstractGraphPanel gp; int scrollBarWidth; @@ -261,12 +67,57 @@ public class GraphDialog extends AppDialog { int frameHeightDiff; + private Graph graph; + + private static final Logger logger = Logger.getLogger(GraphDialog.class.getName()); + public GraphDialog(Window owner, Graph graph, String name) throws InterruptedException { super(owner); + this.graph = graph; setSize(500, 500); Container cnt = getContentPane(); cnt.setLayout(new BorderLayout()); - gp = new GraphPanel(graph); + if (GraphVizGraphPanel.isAvailable()) { + gp = new GraphVizGraphPanel(graph); + } else { + gp = new GraphPanelSimple(graph); + JPanel betterGraphInfo = new JPanel(new FlowLayout()); + JLabel lab = new JLabel("" + translate("graph.better.dot") + ""); + lab.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + lab.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + Main.advancedSettings("paths"); + } + + }); + betterGraphInfo.add(lab); + cnt.add(betterGraphInfo, BorderLayout.SOUTH); + } + + JPopupMenu popupMenu = new JPopupMenu(); + JMenuItem copyMenuItem = new JMenuItem(translate("menu.copygraph.gv")); + copyMenuItem.addActionListener(this::copyToClipBoardActionPerformed); + popupMenu.add(copyMenuItem); + gp.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + maybeShowPopup(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + maybeShowPopup(e); + } + + private void maybeShowPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + popupMenu.show(e.getComponent(), + e.getX(), e.getY()); + } + } + }); + setTitle(translate("graph") + " " + name); JScrollPane scrollPane = new JScrollPane(gp); scrollBarWidth = scrollPane.getVerticalScrollBar().getPreferredSize().width; @@ -282,6 +133,58 @@ public class GraphDialog extends AppDialog { View.setWindowIcon(this); + MouseAdapter ma = new MouseAdapter() { + + private Point origin; + + @Override + public void mousePressed(MouseEvent e) { + origin = new Point(e.getPoint()); + } + + @Override + public void mouseReleased(MouseEvent e) { + } + + @Override + public void mouseDragged(MouseEvent e) { + if (origin != null) { + JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, gp); + if (viewPort != null) { + int deltaX = origin.x - e.getX(); + int deltaY = origin.y - e.getY(); + + Rectangle view = viewPort.getViewRect(); + view.x += deltaX; + view.y += deltaY; + + gp.scrollRectToVisible(view); + } + } + } + + }; + + gp.addMouseListener(ma); + gp.addMouseMotionListener(ma); + } + + private void copyToClipBoardActionPerformed(ActionEvent evt) { + StringBuilder stringBuilder = new StringBuilder(); + try { + StringBuilderTextWriter stringBuilderWriter = new StringBuilderTextWriter(null, stringBuilder); + new PcodeGraphVizExporter().export(graph, stringBuilderWriter); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Error while generating graph", ex); + return; + } + try { + StringSelection stringSelection = new StringSelection(stringBuilder.toString()); + Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard(); + clpbrd.setContents(stringSelection, null); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Cannot copy to clipboard", ex); + } } @Override @@ -327,24 +230,4 @@ public class GraphDialog extends AppDialog { setSize(new Dimension(dim.width + frameWidthDiff, dim.height + frameHeightDiff)); } - private void drawArrow(Graphics g, int x1, int y1, int x2, int y2) { - Polygon arrowHead = new Polygon(); - arrowHead.addPoint(0, 0); - arrowHead.addPoint(-3, -8); - arrowHead.addPoint(3, -8); - Line2D.Double line = new Line2D.Double(x1, y1, x2, y2); - AffineTransform tx = new AffineTransform(); - tx.setToIdentity(); - double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1); - tx.translate(line.x2, line.y2); - tx.rotate((angle - Math.PI / 2d)); - - Graphics2D g2d = (Graphics2D) g; - AffineTransform oldTransform = g2d.getTransform(); - g2d.draw(line); - tx.preConcatenate(oldTransform); - g2d.setTransform(tx); - g2d.fill(arrowHead); - g2d.setTransform(oldTransform); - } } diff --git a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java index 83a2a2b94..a76090dec 100644 --- a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java @@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.DisassemblyListener; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.ActionGraph; +import com.jpexs.decompiler.flash.action.ActionGraphSource; import com.jpexs.decompiler.flash.action.ActionList; import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; import com.jpexs.decompiler.flash.action.deobfuscation.BrokenScriptDetector; @@ -33,6 +34,7 @@ import com.jpexs.decompiler.flash.action.swf4.ActionPush; import com.jpexs.decompiler.flash.action.swf4.ConstantIndex; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.exporters.script.PcodeGraphVizExporter; import com.jpexs.decompiler.flash.gui.AppStrings; import com.jpexs.decompiler.flash.gui.DebugPanel; import com.jpexs.decompiler.flash.gui.DebuggerHandler; @@ -49,8 +51,11 @@ import com.jpexs.decompiler.flash.gui.controls.NoneSelectedButtonGroup; import com.jpexs.decompiler.flash.gui.editor.DebuggableEditorPane; import com.jpexs.decompiler.flash.gui.editor.LinkHandler; import com.jpexs.decompiler.flash.gui.tagtree.TagTreeModel; +import com.jpexs.decompiler.flash.helpers.CodeFormatting; import com.jpexs.decompiler.flash.helpers.HighlightedText; import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; +import com.jpexs.decompiler.flash.helpers.NulWriter; +import com.jpexs.decompiler.flash.helpers.StringBuilderTextWriter; import com.jpexs.decompiler.flash.helpers.hilight.HighlightData; import com.jpexs.decompiler.flash.helpers.hilight.Highlighting; import com.jpexs.decompiler.flash.search.ActionScriptSearch; @@ -59,6 +64,9 @@ import com.jpexs.decompiler.flash.search.ScriptSearchListener; import com.jpexs.decompiler.flash.tags.DoInitActionTag; import com.jpexs.decompiler.flash.tags.base.ASMSource; import com.jpexs.decompiler.graph.CompilationException; +import com.jpexs.decompiler.graph.GraphPart; +import com.jpexs.decompiler.graph.GraphSource; +import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.helpers.CancellableWorker; import com.jpexs.helpers.Helper; import java.awt.BorderLayout; @@ -66,6 +74,9 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Insets; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.io.IOException; import java.io.StringReader; diff --git a/src/com/jpexs/decompiler/flash/gui/graph/AbstractGraphPanel.java b/src/com/jpexs/decompiler/flash/gui/graph/AbstractGraphPanel.java new file mode 100644 index 000000000..db58aeaae --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/graph/AbstractGraphPanel.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010-2018 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.graph; + +import com.jpexs.decompiler.graph.Graph; +import javax.swing.JPanel; + +/** + * + * @author JPEXS + */ +public abstract class AbstractGraphPanel extends JPanel { + + protected Graph graph; + + public AbstractGraphPanel(Graph graph) throws InterruptedException { + this.graph = graph; + graph.init(null); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/graph/GraphPanelSimple.java b/src/com/jpexs/decompiler/flash/gui/graph/GraphPanelSimple.java new file mode 100644 index 000000000..f217671c4 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/graph/GraphPanelSimple.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2018 Jindra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.graph; + +import com.jpexs.decompiler.graph.Graph; +import com.jpexs.decompiler.graph.GraphPart; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +/** + * + * @author Jindra + */ +public class GraphPanelSimple extends AbstractGraphPanel { + + private static final int SPACE_VERTICAL = 16; + + private static final int SPACE_HORIZONTAL = 10; + + private static final int SPACE_BACKLINKS = 5; + + private static final int BLOCK_WIDTH = 200; + + private static final int BLOCK_HEIGHT = 20; + + private final HashMap partPos = new HashMap<>(); + + private final Point size; + + private int backLinksLeft = 0; + + private int backLinksRight = 0; + + private final GraphPart head; + + public GraphPanelSimple(Graph graph) throws InterruptedException { + super(graph); + size = getPartPositions(head = graph.heads.get(0), SPACE_VERTICAL + SPACE_VERTICAL + BLOCK_HEIGHT / 2, getPartWidth(graph.heads.get(0), new HashSet<>()) * (BLOCK_WIDTH + SPACE_HORIZONTAL) / 2 - SPACE_HORIZONTAL, partPos, true); + backLinksLeft = 1; + backLinksRight = 1; + for (GraphPart part : partPos.keySet()) { + Point p = partPos.get(part); + for (GraphPart n : part.nextParts) { + Point npos = partPos.get(n); + if (npos.y < p.y) { + if (p.x > size.x / 2) { + backLinksRight++; + } else { + backLinksLeft++; + } + } + } + } + size.x += 2 * SPACE_BACKLINKS + backLinksLeft * SPACE_BACKLINKS + backLinksRight * SPACE_BACKLINKS; + setPreferredSize(new Dimension(size.x, size.y)); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + Graphics2D g2 = (Graphics2D) g; + /*g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON);*/ + g2.setColor(Color.black); + int startX = SPACE_BACKLINKS + backLinksLeft * SPACE_BACKLINKS; + int blLeft = 0; + int blRight = 0; + for (GraphPart part : partPos.keySet()) { + Point p = partPos.get(part); + g2.setColor(Color.white); + g2.fillRect(startX + p.x - BLOCK_WIDTH / 2, p.y - BLOCK_HEIGHT / 2, BLOCK_WIDTH, BLOCK_HEIGHT); + g2.setColor(Color.black); + g2.drawRect(startX + p.x - BLOCK_WIDTH / 2, p.y - BLOCK_HEIGHT / 2, BLOCK_WIDTH, BLOCK_HEIGHT); + g2.drawString(part.toString(), startX + p.x - g2.getFontMetrics().stringWidth(part.toString()) / 2, p.y + g2.getFontMetrics().getHeight() / 2); + } + + Point hp = partPos.get(head); + drawArrow(g2, startX + hp.x, hp.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL, startX + hp.x, hp.y - BLOCK_HEIGHT / 2); + + for (GraphPart part : partPos.keySet()) { + Point p = partPos.get(part); + + for (int n = 0; n < part.nextParts.size(); n++) { + boolean isIf = part.nextParts.size() == 2; + if (isIf) { + if (n == 0) { + g2.setColor(new Color(0, 0x80, 0)); + } else { + g2.setColor(Color.red); + } + } else { + g2.setColor(Color.black); + } + Point npos = partPos.get(part.nextParts.get(n)); + if (npos.y < p.y) { + int sidex = startX; + if (p.x > size.x / 2) { + blRight++; + sidex = size.x - backLinksRight * SPACE_BACKLINKS; + sidex += SPACE_BACKLINKS + SPACE_BACKLINKS * blRight; + } else { + blLeft++; + sidex -= SPACE_BACKLINKS + SPACE_BACKLINKS * blLeft; + } + g2.drawLine(startX + p.x, p.y + BLOCK_HEIGHT / 2, startX + p.x, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); + g2.drawLine(startX + p.x, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2, sidex, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); + g2.drawLine(sidex, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, sidex, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); + drawArrow(g2, sidex, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2); + g2.drawLine(startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2); + } else { + drawArrow(g2, startX + p.x, p.y + BLOCK_HEIGHT / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2); + g2.drawLine(startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2); + } + } + } + } + + private Point getPartSubPositions(Point ret, int totalWidth, GraphPart part, int y, int x, HashMap used) { + if (used.containsKey(part)) { + Point p = used.get(part); + return new Point(x, y); + } + used.put(part, new Point(x, y)); + if (part.nextParts.size() > 0) { + int cx = x - totalWidth / 2; + for (int p = 0; p < part.nextParts.size(); p++) { + HashSet k = new HashSet<>(); + k.addAll(used.keySet()); + int partWidth = getPartWidth(part.nextParts.get(p), k); + int cellWidth = partWidth * (BLOCK_WIDTH + SPACE_HORIZONTAL); + Point pt = getPartPositions(part.nextParts.get(p), y + BLOCK_HEIGHT + SPACE_VERTICAL, cx + cellWidth / 2, used, false); + if (pt.x > ret.x) { + ret.x = pt.x; + } + if (pt.y > ret.y) { + ret.y = pt.y; + } + cx += cellWidth; + + } + cx = x - totalWidth / 2; + for (int p = 0; p < part.nextParts.size(); p++) { + HashSet k = new HashSet<>(); + k.addAll(used.keySet()); + int cellWidth = getPartWidth(part.nextParts.get(p), k) * (BLOCK_WIDTH + SPACE_HORIZONTAL); + + HashSet hs = new HashSet<>(); + hs.addAll(used.keySet()); + int totalWidthParts2 = getPartWidth(part.nextParts.get(p), hs); + int totalWidth2 = totalWidthParts2 * (BLOCK_WIDTH + SPACE_HORIZONTAL); + + Point pt = getPartSubPositions(ret, totalWidth2, part.nextParts.get(p), y + BLOCK_HEIGHT + SPACE_VERTICAL, cx + cellWidth / 2, used); + if (pt.x > ret.x) { + ret.x = pt.x; + } + if (pt.y > ret.y) { + ret.y = pt.y; + } + cx += cellWidth; + } + + } + return ret; + } + + private Point getPartPositions(GraphPart part, int y, int x, HashMap used, boolean goSub) { + HashMap l = new HashMap<>(); + l.putAll(used); + HashSet hs = new HashSet<>(); + hs.addAll(l.keySet()); + int totalWidthParts = getPartWidth(part, hs); + int totalWidth = totalWidthParts * (BLOCK_WIDTH + SPACE_HORIZONTAL); + + if (used.containsKey(part)) { + Point p = used.get(part); + return new Point(x, y); + } + Point ret = new Point(x - BLOCK_WIDTH / 2 - SPACE_HORIZONTAL / 2 + BLOCK_WIDTH, y + BLOCK_HEIGHT); + if (goSub) { + Point p2 = getPartSubPositions(ret, totalWidth, part, y, x, used); + if (p2.x > ret.x) { + ret.x = p2.x; + } + if (p2.y > ret.y) { + ret.y = p2.y; + } + } + return ret; + } + + private int getPartHeight(GraphPart part, List used) { + if (used.contains(part)) { + return 1; + } + used.add(part); + int maxH = 0; + for (int p = 0; p < part.nextParts.size(); p++) { + int h = getPartHeight(part.nextParts.get(p), used); + if (h > maxH) { + maxH = h; + } + } + return 1 + maxH; + } + + private int getPartWidth(GraphPart part, HashSet used) { + + if (used.contains(part)) { + return 1; + } + used.add(part); + if (part.nextParts.isEmpty()) { + return 1; + } + int w = 0; + for (GraphPart subpart : part.nextParts) { + w += getPartWidth(subpart, used); + } + return w; + } + + private void drawArrow(Graphics g, int x1, int y1, int x2, int y2) { + Polygon arrowHead = new Polygon(); + arrowHead.addPoint(0, 0); + arrowHead.addPoint(-3, -8); + arrowHead.addPoint(3, -8); + Line2D.Double line = new Line2D.Double(x1, y1, x2, y2); + AffineTransform tx = new AffineTransform(); + tx.setToIdentity(); + double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1); + tx.translate(line.x2, line.y2); + tx.rotate((angle - Math.PI / 2d)); + + Graphics2D g2d = (Graphics2D) g; + AffineTransform oldTransform = g2d.getTransform(); + g2d.draw(line); + tx.preConcatenate(oldTransform); + g2d.setTransform(tx); + g2d.fill(arrowHead); + g2d.setTransform(oldTransform); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/graph/GraphVizDotCommands.java b/src/com/jpexs/decompiler/flash/gui/graph/GraphVizDotCommands.java new file mode 100644 index 000000000..50889984e --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/graph/GraphVizDotCommands.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 Jindra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.graph; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import javax.imageio.ImageIO; + +/** + * + * @author JPEXS + */ +public class GraphVizDotCommands { + + public static boolean graphVizAvailable() { + String dotPath = Configuration.graphVizDotLocation.get(); + if (dotPath.isEmpty() || !new File(dotPath).exists()) { + return false; + } + return true; + } + + private static void runCommand(String command) { + try { + Process process = Runtime.getRuntime().exec(command); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String s; + while ((s = reader.readLine()) != null) { + + } + } catch (IOException e) { + //ignore + } + } + + private static boolean runDotCommand(String command) { + String dotLocation = Configuration.graphVizDotLocation.get(); + if (dotLocation.isEmpty() && !new File(dotLocation).exists()) { + return false; + }// + runCommand("\"" + dotLocation + "\" " + command); + return true; + } + + public BufferedImage dotToImage(String text) throws IOException { + File gvFile = File.createTempFile("graphexport", ".gv"); + File pngFile = File.createTempFile("graphexport", ".png"); + + PrintWriter pw = new PrintWriter(gvFile); + pw.println(text); + pw.close(); + String extraParams = " -Nfontname=times-bold -Nfontsize=12"; + if (!runDotCommand("-Tpng" + extraParams + " -o \"" + pngFile.getAbsolutePath() + "\" \"" + gvFile.getAbsolutePath() + "\"")) { + gvFile.delete(); + return null; + } + if (!pngFile.exists()) { + throw new IOException("Dot did not produce any file"); + } + BufferedImage ret = ImageIO.read(pngFile); + gvFile.delete(); + pngFile.delete(); + return ret; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/graph/GraphVizGraphPanel.java b/src/com/jpexs/decompiler/flash/gui/graph/GraphVizGraphPanel.java new file mode 100644 index 000000000..7bad26d68 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/graph/GraphVizGraphPanel.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018 Jindra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.graph; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.exporters.script.PcodeGraphVizExporter; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.helpers.StringBuilderTextWriter; +import com.jpexs.decompiler.graph.Graph; +import com.jpexs.graphs.graphviz.graph.operations.codestructure.CodeStructureModifyOperation; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +/** + * + * @author JPEXS + */ +public class GraphVizGraphPanel extends AbstractGraphPanel { + + private static final Logger logger = Logger.getLogger(GraphVizGraphPanel.class.getName()); + private BufferedImage image; + private JPanel imagePanel; + + public GraphVizGraphPanel(Graph graph) throws InterruptedException { + super(graph); + PcodeGraphVizExporter ex = new PcodeGraphVizExporter(); + StringBuilder sb = new StringBuilder(); + StringBuilderTextWriter sbWriter = new StringBuilderTextWriter(null, sb); + ex.export(graph, sbWriter); + String original = sb.toString(); + CodeStructureModifyOperation structureModify = new CodeStructureModifyOperation(); + String structured = structureModify.execute(original, null); + try { + image = new GraphVizDotCommands().dotToImage(structured); + } catch (IOException ex1) { + logger.log(Level.SEVERE, "Exporting image failed", ex1); + image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + } + setLayout(new BorderLayout()); + imagePanel = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + g.drawImage(image, 0, 0, null); + } + }; + Dimension dim = new Dimension(image.getWidth(), image.getHeight()); + imagePanel.setPreferredSize(dim); + imagePanel.setMinimumSize(dim); + setPreferredSize(dim); + + setLayout(new GridBagLayout()); + add(imagePanel, new GridBagConstraints()); + setBackground(Color.white); + } + + public static boolean isAvailable() { + return GraphVizDotCommands.graphVizAvailable(); + } + +} diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index c1c0e47ac..2e3dd1ca3 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -444,6 +444,9 @@ config.description.showSetAdvanceValuesMessage = Show again information about se config.name.gui.fontSizeMultiplier = Font size multiplier config.description.gui.fontSizeMultiplier = Font size multiplier +config.name.graphVizDotLocation = 5) GraphViz Dot executable path +config.description.graphVizDotLocation = Path to dot.exe (or similar for linux) of GraphViz application for displaying Graphs. + #Do not translate the Font Styles (Plain... etc.) config.name.gui.sourceFont = Source font style config.description.gui.sourceFont = FontName-FontStyle(Plain,Bold,Italic,BoldItalic)-FontSize diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties index cda0f3caa..3b41b7fb0 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties @@ -46,6 +46,7 @@ scripts.pcode_hex = P-code with Hex scripts.hex = Hex scripts.constants = Constants scripts.as_method_stubs = ActionScript method stubs +scripts.pcode_graphviz = P-code GraphViz binaryData = Binary data binaryData.raw = Raw diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_cs.properties index 8644cfa68..bd512fad2 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_cs.properties @@ -42,6 +42,7 @@ scripts = Skripty scripts.as = ActionScript scripts.pcode = P-k\u00f3d scripts.pcode_hex = P-k\u00f3d s hex +scripts.pcode_graphviz = P-k\u00f3d GraphViz scripts.hex = Hex scripts.constants = Konstanty diff --git a/src/com/jpexs/decompiler/flash/gui/locales/GraphDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/GraphDialog.properties index 6c5c8115a..fa1ba5fa5 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/GraphDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/GraphDialog.properties @@ -14,3 +14,5 @@ # along with this program. If not, see . graph = Graph +graph.better.dot = Tip: Configure GraphViz Dot executable path in Advanced settings / Paths (5) to get a way better graphs! +menu.copygraph.gv = Copy GraphViz source to ClipBoard \ No newline at end of file