From 80b000b82b3995ae2b9ffcedb0f29ab3f8a74587 Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Sun, 17 Jul 2016 14:14:26 +0200 Subject: [PATCH] move file offset to the left, setting+button to hide it --- .../jpexs/decompiler/flash/action/Action.java | 2983 +++++++++-------- .../flash/configuration/Configuration.java | 2119 ++++++------ .../flash/exporters/ShapeExporter.java | 2 +- .../flash/gui/action/ActionPanel.java | 2113 ++++++------ .../flash/gui/graphics/fileoffset16.png | Bin 0 -> 554 bytes .../flash/gui/locales/MainFrame.properties | 1 + 6 files changed, 3625 insertions(+), 3593 deletions(-) create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/fileoffset16.png 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 9264b1d60..489f5f072 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 @@ -1,1487 +1,1496 @@ -/* - * Copyright (C) 2010-2016 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.action; - -import com.jpexs.decompiler.flash.AppResources; -import com.jpexs.decompiler.flash.BaseLocalData; -import com.jpexs.decompiler.flash.DisassemblyListener; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.abc.avm2.parser.script.Reference; -import com.jpexs.decompiler.flash.action.deobfuscation.ActionDeobfuscator; -import com.jpexs.decompiler.flash.action.model.ActionItem; -import com.jpexs.decompiler.flash.action.model.ConstantPool; -import com.jpexs.decompiler.flash.action.model.DirectValueActionItem; -import com.jpexs.decompiler.flash.action.model.ExtendsActionItem; -import com.jpexs.decompiler.flash.action.model.FunctionActionItem; -import com.jpexs.decompiler.flash.action.model.GetMemberActionItem; -import com.jpexs.decompiler.flash.action.model.GetPropertyActionItem; -import com.jpexs.decompiler.flash.action.model.GetVariableActionItem; -import com.jpexs.decompiler.flash.action.model.ImplementsOpActionItem; -import com.jpexs.decompiler.flash.action.model.NewObjectActionItem; -import com.jpexs.decompiler.flash.action.model.SetMemberActionItem; -import com.jpexs.decompiler.flash.action.model.SetPropertyActionItem; -import com.jpexs.decompiler.flash.action.model.SetVariableActionItem; -import com.jpexs.decompiler.flash.action.model.StoreRegisterActionItem; -import com.jpexs.decompiler.flash.action.model.TemporaryRegister; -import com.jpexs.decompiler.flash.action.model.clauses.ClassActionItem; -import com.jpexs.decompiler.flash.action.model.clauses.InterfaceActionItem; -import com.jpexs.decompiler.flash.action.parser.ActionParseException; -import com.jpexs.decompiler.flash.action.parser.pcode.ASMParsedSymbol; -import com.jpexs.decompiler.flash.action.parser.pcode.FlasmLexer; -import com.jpexs.decompiler.flash.action.parser.script.VariableActionItem; -import com.jpexs.decompiler.flash.action.special.ActionEnd; -import com.jpexs.decompiler.flash.action.special.ActionStore; -import com.jpexs.decompiler.flash.action.swf4.ActionEquals; -import com.jpexs.decompiler.flash.action.swf4.ActionIf; -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.ActionConstantPool; -import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction; -import com.jpexs.decompiler.flash.action.swf5.ActionEquals2; -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.configuration.Configuration; -import com.jpexs.decompiler.flash.ecma.EcmaScript; -import com.jpexs.decompiler.flash.ecma.Null; -import com.jpexs.decompiler.flash.ecma.Undefined; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.helpers.CodeFormatting; -import com.jpexs.decompiler.flash.helpers.GraphTextWriter; -import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; -import com.jpexs.decompiler.flash.helpers.NulWriter; -import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; -import com.jpexs.decompiler.flash.helpers.collections.MyEntry; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.graph.Graph; -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.TranslateException; -import com.jpexs.decompiler.graph.TranslateStack; -import com.jpexs.decompiler.graph.model.CommentItem; -import com.jpexs.decompiler.graph.model.IfItem; -import com.jpexs.decompiler.graph.model.LocalData; -import com.jpexs.decompiler.graph.model.NotItem; -import com.jpexs.decompiler.graph.model.PopItem; -import com.jpexs.decompiler.graph.model.ScriptEndItem; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.CancellableWorker; -import com.jpexs.helpers.Helper; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Represents one ACTIONRECORD, also has some static method to work with Actions - * - * @author JPEXS - */ -public abstract class Action implements GraphSourceItem { - - private static final int INFORM_LISTENER_RESOLUTION = 100; - - private boolean ignored = false; - - public long fileOffset = -1; - - /** - * Action type identifier - */ - private int actionCode; - - /** - * Length of action data - */ - protected int actionLength; - - private long address; - - @Override - public long getLineOffset() { - return fileOffset; - } - - /** - * Names of ActionScript properties - */ - public static final String[] propertyNames = new String[]{ - "_X", - "_Y", - "_xscale", - "_yscale", - "_currentframe", - "_totalframes", - "_alpha", - "_visible", - "_width", - "_height", - "_rotation", - "_target", - "_framesloaded", - "_name", - "_droptarget", - "_url", - "_highquality", - "_focusrect", - "_soundbuftime", - "_quality", - "_xmouse", - "_ymouse" - }; - - public static final List propertyNamesList = Arrays.asList(propertyNames); - - private static final Logger logger = Logger.getLogger(Action.class.getName()); - - /** - * Constructor - * - * @param actionCode Action type identifier - * @param actionLength Length of action data - */ - public Action(int actionCode, int actionLength) { - this.actionCode = actionCode; - this.actionLength = actionLength; - } - - public Action() { - } - - /** - * Returns address of this action - * - * @return address of this action - */ - @Override - public long getAddress() { - return address; - } - - /** - * Return code of this action - * - * @return code of this action - */ - public int getActionCode() { - return actionCode; - } - - /** - * Gets all addresses which are referenced from this action and/or - * subactions - * - * @param refs list of addresses - */ - public void getRef(Set refs) { - } - - /** - * Gets all addresses which are referenced from the list of actions - * - * @param list List of actions - * @return List of addresses - */ - public static Set getActionsAllRefs(List list) { - Set ret = new HashSet<>(); - for (Action a : list) { - a.getRef(ret); - } - return ret; - } - - public int getTotalActionLength() { - return actionLength + 1 + (actionCode >= 0x80 ? 2 : 0); - } - - /** - * Sets address of this instruction - * - * @param address Address - */ - public void setAddress(long address) { - this.address = address; - } - - /** - * Returns a string representation of the object - * - * @return a string representation of the object. - */ - @Override - public String toString() { - return "Action" + actionCode; - } - - /** - * Reads String from FlasmLexer - * - * @param lex FlasmLexer - * @return String value - * @throws IOException - * @throws ActionParseException When read object is not String - */ - protected String lexString(FlasmLexer lex) throws IOException, ActionParseException { - ASMParsedSymbol symb = lex.yylex(); - if (symb.type != ASMParsedSymbol.TYPE_STRING) { - throw new ActionParseException("String expected", lex.yyline()); - } - return (String) symb.value; - } - - /** - * Reads Block startServer from FlasmLexer - * - * @param lex FlasmLexer - * @throws IOException - * @throws ActionParseException When read object is not Block startServer - */ - protected void lexBlockOpen(FlasmLexer lex) throws IOException, ActionParseException { - ASMParsedSymbol symb = lex.yylex(); - if (symb.type != ASMParsedSymbol.TYPE_BLOCK_START) { - throw new ActionParseException("Block startServer ", lex.yyline()); - } - } - - /** - * Reads Identifier from FlasmLexer - * - * @param lex FlasmLexer - * @return Identifier name - * @throws IOException - * @throws ActionParseException When read object is not Identifier - */ - protected String lexIdentifier(FlasmLexer lex) throws IOException, ActionParseException { - ASMParsedSymbol symb = lex.yylex(); - if (symb.type != ASMParsedSymbol.TYPE_IDENTIFIER) { - throw new ActionParseException("Identifier expected", lex.yyline()); - } - return (String) symb.value; - } - - /** - * Reads long value from FlasmLexer - * - * @param lex FlasmLexer - * @return long value - * @throws IOException - * @throws ActionParseException When read object is not long value - */ - protected long lexLong(FlasmLexer lex) throws IOException, ActionParseException { - ASMParsedSymbol symb = lex.yylex(); - if (symb.type != ASMParsedSymbol.TYPE_INTEGER) { - throw new ActionParseException("Integer expected", lex.yyline()); - } - return (Long) symb.value; - } - - /** - * Reads boolean value from FlasmLexer - * - * @param lex FlasmLexer - * @return boolean value - * @throws IOException - * @throws ActionParseException When read object is not boolean value - */ - protected boolean lexBoolean(FlasmLexer lex) throws IOException, ActionParseException { - ASMParsedSymbol symb = lex.yylex(); - if (symb.type != ASMParsedSymbol.TYPE_BOOLEAN) { - throw new ActionParseException("Boolean expected", lex.yyline()); - } - return (Boolean) symb.value; - } - - /** - * Gets action converted to bytes - * - * @param version SWF version - * @return Array of bytes - */ - public final byte[] getBytes(int version) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(baos, version); - try { - getContentBytes(sos); - sos.close(); - } catch (IOException e) { - throw new Error("This should never happen.", e); - } - return surroundWithAction(baos.toByteArray(), version); - } - - protected void getContentBytes(SWFOutputStream sos) throws IOException { - } - - /** - * Gets the length of action converted to bytes - * - * @return Length - */ - public final int getBytesLength() { - return getContentBytesLength() + (actionCode >= 0x80 ? 3 : 1); - } - - protected int getContentBytesLength() { - return 0; - } - - /** - * Updates the action length to the length calculated from action bytes - */ - public void updateLength() { - int length = getBytesLength(); - actionLength = length - 1 - (actionCode >= 0x80 ? 2 : 0); - } - - /** - * Surrounds byte array with Action header - * - * @param data Byte array - * @param version SWF version - * @return Byte array - */ - private byte[] surroundWithAction(byte[] data, int version) { - ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); - SWFOutputStream sos2 = new SWFOutputStream(baos2, version); - try { - sos2.writeUI8(actionCode); - if (actionCode >= 0x80) { - sos2.writeUI16(data.length); - } - sos2.write(data); - sos2.close(); - } catch (IOException e) { - throw new Error("This should never happen.", e); - } - return baos2.toByteArray(); - } - - @Override - public long getFileOffset() { - return fileOffset; - } - - /** - * Converts list of Actions to bytes - * - * @param list List of actions - * @param addZero Whether or not to add 0 UI8 value to the end - * @param version SWF version - * @return Array of bytes - */ - public static byte[] actionsToBytes(List list, boolean addZero, int version) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Action lastAction = null; - for (Action a : list) { - try { - lastAction = a; - baos.write(a.getBytes(version)); - } catch (IOException e) { - } - } - if (addZero && (lastAction == null || !(lastAction instanceof ActionEnd))) { - baos.write(0); - } - return baos.toByteArray(); - } - - public static ByteArrayRange actionsToByteArrayRange(List list, boolean addZero, int version) { - byte[] bytes = Action.actionsToBytes(list, addZero, version); - return new ByteArrayRange(bytes); - } - - public static void setConstantPools(ASMSource src, List> constantPools, boolean tryInline) throws ConstantPoolTooBigException { - try { - ActionList actions = src.getActions(); - int poolIdx = 0; - for (Action action : actions) { - if (action instanceof ActionConstantPool) { - ActionConstantPool cPool = (ActionConstantPool) action; - List constantPool = constantPools.get(poolIdx); - - int size = ActionConstantPool.calculateSize(constantPool); - if (size > 0xffff && tryInline) { - for (int i = 0; i < constantPool.size(); i++) { - int refCount = actions.getConstantPoolIndexReferenceCount(i); - if (refCount == 1) { - actions.inlineConstantPoolString(i, constantPool.get(i)); - constantPool.set(i, ""); - } - } - - size = ActionConstantPool.calculateSize(constantPool); - } - - if (size > 0xffff) { - throw new ConstantPoolTooBigException(poolIdx, size); - } - - cPool.constantPool = constantPool; - - poolIdx++; - if (constantPools.size() <= poolIdx) { - break; - } - } - } - - actions.removeNonReferencedConstantPoolItems(); - src.setActions(actions); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - - /** - * Set addresses of actions in the list - * - * @param list List of actions - * @param baseAddress Address of first action in the list - */ - public static void setActionsAddresses(List list, long baseAddress) { - long offset = baseAddress; - for (Action a : list) { - a.setAddress(offset); - offset += a.getTotalActionLength(); - } - } - - /** - * Converts list of actions to ASM source - * - * @param listeners - * @param address - * @param list List of actions - * @param version SWF version - * @param exportMode PCode or hex? - * @return source ASM - * - */ - public static String actionsToString(List listeners, long address, ActionList list, int version, ScriptExportMode exportMode) { - HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); - actionsToString(listeners, address, list, version, exportMode, writer); - return writer.toString(); - } - - private static void informListeners(List listeners, int pos, int count) { - if (pos % INFORM_LISTENER_RESOLUTION == 0) { - DisassemblyListener[] listenersArray = listeners.toArray(new DisassemblyListener[listeners.size()]); - for (DisassemblyListener listener : listenersArray) { - listener.progressToString(pos + 1, count); - } - } - } - - /** - * Converts list of actions to ASM source - * - * @param listeners - * @param address - * @param list List of actions - * @param version SWF version - * @param exportMode PCode or hex? - * @param writer - * @return GraphTextWriter - */ - public static GraphTextWriter actionsToString(List listeners, long address, ActionList list, int version, ScriptExportMode exportMode, GraphTextWriter writer) { - if (exportMode == ScriptExportMode.CONSTANTS) { - return constantPoolActionsToString(listeners, address, list, version, exportMode, writer); - } - - long offset; - Set importantOffsets = getActionsAllRefs(list); - /*List cps = SWFInputStream.getConstantPool(new ArrayList(), new ActionGraphSource(list, version, new HashMap(), new HashMap(), new HashMap()), 0, version, path); - if (!cps.isEmpty()) { - setConstantPool(list, cps.get(cps.size() - 1)); - }*/ - HashMap> containers = new HashMap<>(); - HashMap containersPos = new HashMap<>(); - offset = address; - int pos = 0; - boolean lastPush = false; - byte[] fileData = list.fileData; - for (Action a : list) { - informListeners(listeners, pos, list.size()); - - if (exportMode == ScriptExportMode.PCODE_HEX) { - if (lastPush) { - writer.newLine(); - lastPush = false; - } - writer.appendNoHilight("; "); - byte[] bytes = a.getBytes(version); - writer.appendNoHilight(Helper.bytesToHexString(bytes)); - if (Configuration.showOriginalBytesInPcodeHex.get()) { - long fileOffset = a.getFileOffset(); - if (fileData != null && fileOffset != -1 && fileData.length > fileOffset + bytes.length - 1) { - writer.appendNoHilight(" ("); - boolean same = true; - for (int i = 0; i < bytes.length; i++) { - byte b = fileData[(int) (fileOffset + i)]; - if (b != bytes[i]) { - same = false; - break; - } - } - - if (!same) { - for (int i = 0; i < bytes.length; i++) { - writer.appendNoHilight(Helper.byteToHex(fileData[(int) (fileOffset + i)])); - writer.appendNoHilight(" "); - } - } - - writer.appendNoHilight("@"); - writer.appendNoHilight(Helper.formatHex(fileOffset, 8)); - writer.appendNoHilight(")"); - } - } - - writer.newLine(); - } - - offset = a.getAddress(); - - if ((!(a.isIgnored())) && (a instanceof GraphSourceItemContainer)) { - GraphSourceItemContainer cnt = (GraphSourceItemContainer) a; - containersPos.put(cnt, 0); - List sizes = cnt.getContainerSizes(); - long addr = ((Action) cnt).getAddress() + cnt.getHeaderSize(); - for (Long size : sizes) { - addr += size; - if (size == 0) { - continue; - } - if (!containers.containsKey(addr)) { - containers.put(addr, new ArrayList<>()); - } - containers.get(addr).add(cnt); - } - } - - if (containers.containsKey(offset)) { - for (int i = 0; i < containers.get(offset).size(); i++) { - if (lastPush) { - writer.newLine(); - lastPush = false; - } - writer.appendNoHilight("}").newLine(); - GraphSourceItemContainer cnt = containers.get(offset).get(i); - int cntPos = containersPos.get(cnt); - writer.appendNoHilight(cnt.getASMSourceBetween(cntPos)); - cntPos++; - containersPos.put(cnt, cntPos); - } - } - - if (Configuration.showAllAddresses.get() || importantOffsets.contains(offset)) { - if (lastPush) { - writer.newLine(); - lastPush = false; - } - writer.appendNoHilight("loc"); - writer.appendNoHilight(Helper.formatAddress(offset)); - writer.appendNoHilight(":"); - } - - if (a.isIgnored()) { - if (lastPush) { - writer.newLine(); - lastPush = false; - } - if (!(a instanceof ActionEnd)) { - int len = a.getTotalActionLength(); - for (int i = 0; i < len; i++) { - writer.appendNoHilight("Nop").newLine(); - } - } - } else { - //if (!(a instanceof ActionNop)) { - String add = ""; - // honfika: commented out the following lines, because it makes no sense - /*if (a instanceof ActionIf) { - add = " change: " + ((ActionIf) a).getJumpOffset(); - } - if (a instanceof ActionJump) { - add = " change: " + ((ActionJump) a).getJumpOffset(); - } - add = "; ofs" + Helper.formatAddress(offset) + add; - add = "";*/ - if ((a instanceof ActionPush) && lastPush) { - writer.appendNoHilight(" "); - ((ActionPush) a).paramsToStringReplaced(list, importantOffsets, exportMode, writer); - } else { - if (lastPush) { - writer.newLine(); - //lastPush = false; - } - - writer.append("", offset, a.getFileOffset()); - - int fixBranch = -1; - if (a instanceof ActionIf) { - ActionIf aif = (ActionIf) a; - if (aif.jumpUsed && !aif.ignoreUsed) { - fixBranch = 0; - } - if (!aif.jumpUsed && aif.ignoreUsed) { - fixBranch = 1; - } - } - - if (fixBranch > -1) { - writer.appendNoHilight("FFDec_DeobfuscatePop"); - if (fixBranch == 0) { //jump - writer.newLine(); - writer.appendNoHilight("Jump loc"); - writer.appendNoHilight(Helper.formatAddress(((ActionIf) a).getTargetAddress())); - } else { - //nojump, ignore - } - } else { - a.getASMSourceReplaced(list, importantOffsets, exportMode, writer); - } - writer.appendNoHilight(a.isIgnored() ? "; ignored" : ""); - writer.appendNoHilight(add); - if (!(a instanceof ActionPush)) { - writer.newLine(); - } - } - lastPush = a instanceof ActionPush; - //} - } - - offset += a.getTotalActionLength(); - pos++; - } - if (lastPush) { - writer.newLine(); - } - - if (containers.containsKey(offset)) { - for (int i = 0; i < containers.get(offset).size(); i++) { - writer.appendNoHilight("}"); - writer.newLine(); - GraphSourceItemContainer cnt = containers.get(offset).get(i); - int cntPos = containersPos.get(cnt); - writer.appendNoHilight(cnt.getASMSourceBetween(cntPos)); - cntPos++; - containersPos.put(cnt, cntPos); - } - } - - if (importantOffsets.contains(offset)) { - writer.appendNoHilight("loc"); - writer.appendNoHilight(Helper.formatAddress(offset)); - writer.appendNoHilight(":"); - writer.newLine(); - } - - return writer; - } - - public static GraphTextWriter constantPoolActionsToString(List listeners, long address, ActionList list, int version, ScriptExportMode exportMode, GraphTextWriter writer) { - int poolIdx = 0; - writer.appendNoHilight(Helper.constants).newLine(); - for (Action a : list) { - if (a instanceof ActionConstantPool) { - if (poolIdx > 0) { - writer.appendNoHilight("---").newLine(); - } - - ActionConstantPool cPool = (ActionConstantPool) a; - int constIdx = 0; - for (String c : cPool.constantPool) { - writer.appendNoHilight(constIdx); - writer.appendNoHilight("|"); - writer.appendNoHilight(Helper.escapeString(c)); - writer.newLine(); - constIdx++; - } - - poolIdx++; - } - } - - return writer; - } - - /** - * Convert action to ASM source - * - * @param container - * @param knownAddreses List of important offsets to mark as labels - * @param exportMode PCode or hex? - * @return String of P-code source - */ - public String getASMSource(ActionList container, Set knownAddreses, ScriptExportMode exportMode) { - return toString(); - } - - public abstract boolean execute(LocalDataArea lda); - - /* { - //throw new UnsupportedOperationException("Action " + toString() + " not implemented"); - return false; - }*/ - /** - * Translates this function to stack and output. - * - * @param lineStartIns Line start instruction - * @param stack Stack - * @param output Output - * @param regNames Register names - * @param variables Variables - * @param functions Functions - * @param staticOperation the value of staticOperation - * @param path the value of path - * @throws java.lang.InterruptedException - */ - public void translate(GraphSourceItem lineStartIns, TranslateStack stack, List output, HashMap regNames, HashMap variables, HashMap functions, int staticOperation, String path) throws InterruptedException { - } - - @Override - public int getStackPopCount(BaseLocalData localData, TranslateStack stack) { - return 0; - } - - @Override - public int getStackPushCount(BaseLocalData localData, TranslateStack stack) { - return 0; - } - - /** - * Pops long value off the stack - * - * @param stack Stack - * @return long value - */ - protected long popLong(TranslateStack stack) { - GraphTargetItem item = stack.pop(); - if (item instanceof DirectValueActionItem) { - return (long) (double) EcmaScript.toNumberAs2(((DirectValueActionItem) item).value); - } - - return 0; - } - - /** - * Converts action index to address in the specified list of actions - * - * @param actions List of actions - * @param ip Action index - * @return address - */ - public static long ip2adr(List actions, int ip) { - /* List actions=new ArrayList(); - for(GraphSourceItem s:sources){ - if(s instanceof Action){ - actions.add((Action)s); - } - }*/ - if (ip >= actions.size()) { - if (actions.isEmpty()) { - return 0; - } - return actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getTotalActionLength(); - } - if (ip == -1) { - return 0; - } - return actions.get(ip).getAddress(); - } - - /** - * Converts address to action index in the specified list of actions - * - * @param actions List of actions - * @param addr Address - * @return action index - */ - public static int adr2ip(List actions, long addr) { - for (int ip = 0; ip < actions.size(); ip++) { - if (actions.get(ip).getAddress() == addr) { - return ip; - } - } - if (actions.size() > 0) { - long outpos = actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getTotalActionLength(); - if (addr == outpos) { - return actions.size(); - } - } - return -1; - } - - public static List actionsToTree(List actions, int version, int staticOperation, String path) throws InterruptedException { - return actionsToTree(new HashMap<>(), new HashMap<>(), new HashMap<>(), actions, version, staticOperation, path); - } - - /** - * Converts list of actions to ActionScript source code - * - * @param asm - * @param actions List of actions - * @param path - * @return source - * @throws java.lang.InterruptedException - */ - public static String actionsToSource(final ASMSource asm, final List actions, final String path) throws InterruptedException { - HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); - actionsToSource(asm, actions, path, writer); - return writer.toString(); - } - - /** - * Converts list of actions to ActionScript source code - * - * @param asm - * @param actions List of actions - * @param path - * @param writer - * @throws java.lang.InterruptedException - */ - public static void actionsToSource(final ASMSource asm, final List actions, final String path, GraphTextWriter writer) throws InterruptedException { - writer.suspendMeasure(); - List tree = null; - Throwable convertException = null; - int timeout = Configuration.decompilationTimeoutSingleMethod.get(); - final SWF swf = asm == null ? null : asm.getSwf(); - final int version = swf == null ? SWF.DEFAULT_VERSION : swf.version; - try { - tree = CancellableWorker.call(new Callable>() { - @Override - public List call() throws Exception { - int staticOperation = Graph.SOP_USE_STATIC; //(Boolean) Configuration.getConfig("autoDeobfuscate", true) ? Graph.SOP_SKIP_STATIC : Graph.SOP_USE_STATIC; - List tree = actionsToTree(new HashMap<>(), new HashMap<>(), new HashMap<>(), actions, version, staticOperation, path); - SWFDecompilerPlugin.fireActionTreeCreated(tree, swf); - if (Configuration.autoDeobfuscate.get()) { - new ActionDeobfuscator().actionTreeCreated(tree, swf); - } - - Graph.graphToString(tree, new NulWriter(), new LocalData()); - return tree; - } - }, timeout, TimeUnit.SECONDS); - } catch (InterruptedException ex) { - throw ex; - } catch (Exception | OutOfMemoryError | StackOverflowError ex) { - - convertException = ex; - Throwable cause = ex.getCause(); - if (ex instanceof ExecutionException && cause instanceof Exception) { - convertException = cause; - } - if (convertException instanceof TimeoutException) { - logger.log(Level.SEVERE, "Decompilation timeout in: " + path, convertException); - } else { - logger.log(Level.SEVERE, "Decompilation error in: " + path, convertException); - } - - } - writer.continueMeasure(); - - if (asm != null) { - asm.getActionSourcePrefix(writer); - } - if (convertException == null) { - Graph.graphToString(tree, writer, new LocalData()); - } else if (convertException instanceof TimeoutException) { - Helper.appendTimeoutCommentAs2(writer, timeout, actions.size()); - } else { - Helper.appendErrorComment(writer, convertException); - } - if (asm != null) { - asm.getActionSourceSuffix(writer); - } - } - - /** - * Converts list of actions to List of treeItems - * - * @param regNames Register names - * @param variables - * @param functions - * @param actions List of actions - * @param version SWF version - * @param staticOperation - * @param path - * @return List of treeItems - * @throws java.lang.InterruptedException - */ - public static List actionsToTree(HashMap regNames, HashMap variables, HashMap functions, List actions, int version, int staticOperation, String path) throws InterruptedException { - return ActionGraph.translateViaGraph(regNames, variables, functions, actions, version, staticOperation, path); - } - - @Override - public void translate(BaseLocalData localData, TranslateStack stack, List output, int staticOperation, String path) throws InterruptedException { - ActionLocalData aLocalData = (ActionLocalData) localData; - /*int expectedSize = stack.size() - getStackPopCount(localData, stack); - if (expectedSize < 0) { - expectedSize = 0; - } - expectedSize += getStackPushCount(localData, stack);*/ - - translate(aLocalData.lineStartAction, stack, output, aLocalData.regNames, aLocalData.variables, aLocalData.functions, staticOperation, path); - /*if (stack.size() != expectedSize && !(this instanceof ActionPushDuplicate)) { - throw new Error("HONFIKA stack size mismatch"); - }*/ - } - - @Override - public boolean isJump() { - return false; - } - - @Override - public boolean isBranch() { - return false; - } - - @Override - public boolean isExit() { - return false; - } - - @Override - public List getBranches(GraphSource code) { - return new ArrayList<>(); - } - - @Override - public boolean isIgnored() { - return ignored; - } - - @Override - public void setIgnored(boolean ignored, int pos) { - this.ignored = ignored; - } - - public static List actionsPartToTree(Reference fi, HashMap registerNames, HashMap variables, HashMap functions, TranslateStack stack, List actions, int start, int end, int version, int staticOperation, String path) throws InterruptedException { - if (start < actions.size() && (end > 0) && (start > 0)) { - logger.log(Level.FINE, "Entering {0}-{1}{2}", new Object[]{start, end, actions.size() > 0 ? (" (" + actions.get(start).toString() + " - " + actions.get(end == actions.size() ? end - 1 : end) + ")") : ""}); - } - ActionLocalData localData = new ActionLocalData(registerNames, variables, functions); - localData.lineStartAction = fi.getVal(); - List output = new ArrayList<>(); - int ip = start; - boolean isWhile = false; - boolean isForIn = false; - GraphTargetItem inItem = null; - int loopStart = 0; - loopip: - while (ip <= end) { - - long addr = ip2adr(actions, ip); - if (ip > end) { - break; - } - if (ip >= actions.size()) { - output.add(new ScriptEndItem()); - break; - } - if (Configuration.simplifyExpressions.get()) { - stack.simplify(); - } - Action action = actions.get(ip); - if (action.isIgnored()) { - ip++; - continue; - } - - //FunctionActionItem after DefineFunction(/2) are left on the stack. For linestart offsets we consider this kind of stack empty. - boolean isStackEmpty = true; - for (int i = 0; i < stack.size(); i++) { - if ((!(stack.get(i) instanceof FunctionActionItem))) { - isStackEmpty = false; - break; - } - } - - if (isStackEmpty) { - localData.lineStartAction = action; - fi.setVal(action); - } - if (action instanceof GraphSourceItemContainer) { - GraphSourceItemContainer cnt = (GraphSourceItemContainer) action; - //List out=actionsPartToTree(new HashMap(), new HashMap(),new HashMap(), new TranslateStack(), src, ip+1,endip-1 , version); - long endAddr = action.getAddress() + cnt.getHeaderSize(); - String cntName = cnt.getName(); - List> outs = new ArrayList<>(); - HashMap variables2 = Helper.deepCopy(variables); - if (cnt instanceof ActionDefineFunction || cnt instanceof ActionDefineFunction2) { - for (int r = 0; r < 256; r++) { - if (variables2.containsKey("__register" + r)) { - variables2.remove("__register" + r); - } - } - } - for (long size : cnt.getContainerSizes()) { - if (size == 0) { - outs.add(new ArrayList<>()); - continue; - } - List out; - try { - HashMap regNames = cnt.getRegNames(); - if (action instanceof ActionWith || action instanceof ActionTry) { - for (Map.Entry e : registerNames.entrySet()) { - if (!regNames.containsKey(e.getKey())) { - regNames.put(e.getKey(), e.getValue()); - } - } - } - out = ActionGraph.translateViaGraph(regNames, variables2, functions, actions.subList(adr2ip(actions, endAddr), adr2ip(actions, endAddr + size)), version, staticOperation, path + (cntName == null ? "" : "/" + cntName)); - } catch (OutOfMemoryError | TranslateException | StackOverflowError ex) { - logger.log(Level.SEVERE, "Decompilation error in: " + path, ex); - if (ex instanceof OutOfMemoryError) { - Helper.freeMem(); - } - - out = new ArrayList<>(); - out.add(new CommentItem(new String[]{ - "", - " * " + AppResources.translate("decompilationError"), - " * " + AppResources.translate("decompilationError.obfuscated"), - Helper.decompilationErrorAdd == null ? null : " * " + Helper.decompilationErrorAdd, - " * " + AppResources.translate("decompilationError.errorType") + ": " - + ex.getClass().getSimpleName(), - ""})); - } - outs.add(out); - endAddr += size; - } - - ((GraphSourceItemContainer) action).translateContainer(outs, action, stack, output, registerNames, variables, functions); - ip = adr2ip(actions, endAddr); - continue; - } - - //return in for..in - if ((action instanceof ActionPush) && (((ActionPush) action).values.size() == 1) && (((ActionPush) action).values.get(0) == Null.INSTANCE)) { - if (ip + 3 <= end) { - if ((actions.get(ip + 1) instanceof ActionEquals) || (actions.get(ip + 1) instanceof ActionEquals2)) { - if (actions.get(ip + 2) instanceof ActionNot) { - if (actions.get(ip + 3) instanceof ActionIf) { - ActionIf aif = (ActionIf) actions.get(ip + 3); - if (adr2ip(actions, ip2adr(actions, ip + 4) + aif.getJumpOffset()) == ip) { - ip += 4; - continue; - } - } - } - } - } - } - - /*ActionJump && ActionIf removed*/ - /*if ((action instanceof ActionEnumerate2) || (action instanceof ActionEnumerate)) { - loopStart = ip + 1; - isForIn = true; - ip += 4; - action.translate(localData, stack, output); - EnumerateActionItem en = (EnumerateActionItem) stack.peek(); - inItem = en.object; - continue; - } else*/ /*if (action instanceof ActionTry) { - ActionTry atry = (ActionTry) action; - List tryCommands = ActionGraph.translateViaGraph(registerNames, variables, functions, atry.tryBody, version); - ActionItem catchName; - if (atry.catchInRegisterFlag) { - catchName = new DirectValueActionItem(atry, -1, new RegisterNumber(atry.catchRegister), new ArrayList<>()); - } else { - catchName = new DirectValueActionItem(atry, -1, atry.catchName, new ArrayList<>()); - } - List catchExceptions = new ArrayList(); - catchExceptions.add(catchName); - List> catchCommands = new ArrayList>(); - catchCommands.add(ActionGraph.translateViaGraph(registerNames, variables, functions, atry.catchBody, version)); - List finallyCommands = ActionGraph.translateViaGraph(registerNames, variables, functions, atry.finallyBody, version); - output.add(new TryActionItem(tryCommands, catchExceptions, catchCommands, finallyCommands)); - } else if (action instanceof ActionWith) { - ActionWith awith = (ActionWith) action; - List withCommands = ActionGraph.translateViaGraph(registerNames, variables, functions,new ArrayList() , version); //TODO:parse with actions - output.add(new WithActionItem(action, stack.pop(), withCommands)); - } else */ if (false) { - } /*if (action instanceof ActionStoreRegister) { - if ((ip + 1 <= end) && (actions.get(ip + 1) instanceof ActionPop)) { - action.translate(localData, stack, output); - stack.pop(); - ip++; - } else { - try { - action.translate(localData, stack, output); - } catch (Exception ex) { - // ignore - } - } - } */ /*else if (action instanceof ActionStrictEquals) { - if ((ip + 1 < actions.size()) && (actions.get(ip + 1) instanceof ActionIf)) { - List caseValues = new ArrayList(); - List> caseCommands = new ArrayList>(); - caseValues.add(stack.pop()); - ActionItem switchedObject = stack.pop(); - if (output.size() > 0) { - if (output.get(output.size() - 1) instanceof StoreRegisterActionItem) { - output.remove(output.size() - 1); - } - } - int caseStart = ip + 2; - List caseBodyIps = new ArrayList(); - long defaultAddr = 0; - caseBodyIps.add(adr2ip(actions, ((ActionIf) actions.get(ip + 1)).getRef(version), version)); - ip++; - do { - ip++; - if ((actions.get(ip - 1) instanceof ActionStrictEquals) && (actions.get(ip) instanceof ActionIf)) { - caseValues.add(actionsToStackTree(registerNames, jumpsOrIfs, actions, constants, caseStart, ip - 2, version).pop()); - caseStart = ip + 1; - caseBodyIps.add(adr2ip(actions, ((ActionIf) actions.get(ip)).getRef(version), version)); - if (actions.get(ip + 1) instanceof ActionJump) { - defaultAddr = ((ActionJump) actions.get(ip + 1)).getRef(version); - ip = adr2ip(actions, defaultAddr, version); - break; - } - } - } while (ip < end); - - for (int i = 0; i < caseBodyIps.size(); i++) { - int caseEnd = ip - 1; - if (i < caseBodyIps.size() - 1) { - caseEnd = caseBodyIps.get(i + 1) - 1; - } - caseCommands.add(actionsToTree(registerNames, unknownJumps, loopList, jumpsOrIfs, stack, constants, actions, caseBodyIps.get(i), caseEnd, version)); - } - output.add(new SwitchActionItem(action, defaultAddr, switchedObject, caseValues, caseCommands, null)); - continue; - } else { - action.translate(stack, constants, output, registerNames); - } - } */ else { - - if (action instanceof ActionStore) { - ActionStore store = (ActionStore) action; - store.setStore(actions.subList(ip + 1, ip + 1 + store.getStoreSize())); - ip = ip + 1 + store.getStoreSize() - 1/*ip++ will be next*/; - } - - action.translate(localData, stack, output, staticOperation, path); - } - - ip++; - } - //output = checkClass(output); - logger.log(Level.FINE, "Leaving {0}-{1}", new Object[]{start, end}); - return output; - } - - public static GraphTargetItem getWithoutGlobal(GraphTargetItem ti) { - GraphTargetItem t = ti; - if (!(t instanceof GetMemberActionItem)) { - return ti; - } - GetMemberActionItem lastMember = null; - while (((GetMemberActionItem) t).object instanceof GetMemberActionItem) { - lastMember = (GetMemberActionItem) t; - t = ((GetMemberActionItem) t).object; - } - if (((GetMemberActionItem) t).object instanceof GetVariableActionItem) { - GetVariableActionItem v = (GetVariableActionItem) ((GetMemberActionItem) t).object; - if (v.name instanceof DirectValueActionItem) { - if (((DirectValueActionItem) v.name).value instanceof String) { - if (((DirectValueActionItem) v.name).value.equals("_global")) { - GetVariableActionItem gvt = new GetVariableActionItem(null, null, ((GetMemberActionItem) t).memberName); - if (lastMember == null) { - return gvt; - } else { - lastMember.object = gvt; - } - } - } - } - } - return ti; - } - - public static List checkClass(List output) { - if (true) { - //return output; - } - List ret = new ArrayList<>(); - List functions = new ArrayList<>(); - List staticFunctions = new ArrayList<>(); - List> vars = new ArrayList<>(); - List> staticVars = new ArrayList<>(); - GraphTargetItem className; - GraphTargetItem extendsOp = null; - List implementsOp = new ArrayList<>(); - boolean ok = true; - int prevCount = 0; - for (GraphTargetItem t : output) { - if (t instanceof IfItem) { - IfItem it = (IfItem) t; - if (it.expression instanceof NotItem) { - NotItem nti = (NotItem) it.expression; - if ((nti.value instanceof GetMemberActionItem) || (nti.value instanceof GetVariableActionItem)) { - if (true) { //it.onFalse.isEmpty()){ //||(it.onFalse.get(0) instanceof UnsupportedActionItem)) { - if ((it.onTrue.size() == 1) && (it.onTrue.get(0) instanceof SetMemberActionItem) && (((SetMemberActionItem) it.onTrue.get(0)).value instanceof NewObjectActionItem)) { - // ignore - } else { - List parts = it.onTrue; - className = getWithoutGlobal(nti.value); - if (parts.size() >= 1) { - int ipos = 0; - - while ((parts.get(ipos) instanceof PopItem) || ((parts.get(ipos) instanceof IfItem) && ((((IfItem) parts.get(ipos)).onTrue.size() == 1) && (((IfItem) parts.get(ipos)).onTrue.get(0) instanceof SetMemberActionItem) && (((SetMemberActionItem) ((IfItem) parts.get(ipos)).onTrue.get(0)).value instanceof NewObjectActionItem)))) { - ipos++; - } - if (parts.get(ipos) instanceof ExtendsActionItem) { - ExtendsActionItem et = (ExtendsActionItem) parts.get(ipos); - extendsOp = getWithoutGlobal(et.superclass); - ipos++; - } - if (parts.get(ipos) instanceof StoreRegisterActionItem) { - StoreRegisterActionItem sr = (StoreRegisterActionItem) parts.get(ipos); - int instanceReg = sr.register.number; - if (sr.value instanceof GetMemberActionItem) { - GetMemberActionItem gm = (GetMemberActionItem) sr.value; - //gm.memberName should be "prototype" - if (gm.object instanceof TemporaryRegister) { - TemporaryRegister tm = (TemporaryRegister) gm.object; - int classReg = tm.getRegId(); - if (tm.value instanceof SetMemberActionItem) { - SetMemberActionItem sm = (SetMemberActionItem) tm.value; - if (sm.value instanceof StoreRegisterActionItem) { - sr = (StoreRegisterActionItem) sm.value; - if (sr.value instanceof FunctionActionItem) { - ((FunctionActionItem) (sr.value)).calculatedFunctionName = (className instanceof GetMemberActionItem) ? ((GetMemberActionItem) className).memberName : className; - functions.add((FunctionActionItem) sr.value); - - for (; ipos < parts.size(); ipos++) { - if (parts.get(ipos) instanceof ImplementsOpActionItem) { - ImplementsOpActionItem io = (ImplementsOpActionItem) parts.get(ipos); - implementsOp = io.superclasses; - continue; - } - if (parts.get(ipos) instanceof SetMemberActionItem) { - sm = (SetMemberActionItem) parts.get(ipos); - int rnum = -1; - if (sm.object instanceof DirectValueActionItem) { - DirectValueActionItem dv = (DirectValueActionItem) sm.object; - if (dv.value instanceof RegisterNumber) { - RegisterNumber rn = (RegisterNumber) dv.value; - rnum = rn.number; - } - } - if (sm.object instanceof TemporaryRegister) { - rnum = ((TemporaryRegister) sm.object).getRegId(); - } - if (rnum == instanceReg) { - if (sm.value instanceof FunctionActionItem) { - ((FunctionActionItem) sm.value).calculatedFunctionName = sm.objectName; - functions.add((FunctionActionItem) sm.value); - } else { - vars.add(new MyEntry<>(sm.objectName, sm.value)); - } - } else if (rnum == classReg) { - if (sm.value instanceof FunctionActionItem) { - ((FunctionActionItem) sm.value).calculatedFunctionName = sm.objectName; - staticFunctions.add((FunctionActionItem) sm.value); - } else { - staticVars.add(new MyEntry<>(sm.objectName, sm.value)); - } - } - - } - } - - } - - } - } - List output2 = new ArrayList<>(); - for (int i = 0; i < prevCount; i++) { - output2.add(output.get(i)); - } - output2.add(new ClassActionItem(className, extendsOp, implementsOp, null/*FIXME*/, functions, vars, staticFunctions, staticVars)); - return output2; - } - } - } else if (parts.get(ipos) instanceof SetMemberActionItem) { - SetMemberActionItem sm = (SetMemberActionItem) parts.get(0); - if (sm.value instanceof FunctionActionItem) { - FunctionActionItem f = (FunctionActionItem) sm.value; - if (f.actions.isEmpty()) { - if (parts.size() == 2) { - if (parts.get(1) instanceof ImplementsOpActionItem) { - ImplementsOpActionItem iot = (ImplementsOpActionItem) parts.get(1); - implementsOp = iot.superclasses; - } else { - //ok = false; - break; - } - } - List output2 = new ArrayList<>(); - for (int i = 0; i < prevCount; i++) { - output2.add(output.get(i)); - } - output2.add(new InterfaceActionItem(sm.objectName, implementsOp)); - return output2; - } - } - } - } - } - } else { - //ok = false; - } - } else { - ok = false; - } - } else { - ok = false; - } - } else if (!(t instanceof PopItem)) { - prevCount++; - //ok = false; - } - if (!ok) { - break; - } - } - return output; - } - - @Override - public boolean ignoredLoops() { - return false; - } - - public static void setConstantPool(List actions, ConstantPool cpool) { - for (GraphSourceItem a : actions) { - if (a instanceof ActionPush) { - if (cpool != null) { - ((ActionPush) a).constantPool = cpool.constants; - } - } - if (a instanceof ActionDefineFunction) { - if (cpool != null) { - //((ActionDefineFunction) a).setConstantPool(cpool.constants,actions); - } - } - if (a instanceof ActionDefineFunction2) { - if (cpool != null) { - //((ActionDefineFunction2) a).setConstantPool(cpool.constants,actions); - } - } - } - } - - public GraphTextWriter getASMSourceReplaced(ActionList container, Set knownAddreses, ScriptExportMode exportMode, GraphTextWriter writer) { - writer.appendNoHilight(getASMSource(container, knownAddreses, exportMode)); - return writer; - } - - public static double toFloatPoint(Object o) { - if (o instanceof Double) { - return (Double) o; - } - if (o instanceof Integer) { - return (Integer) o; - } - if (o instanceof Long) { - return (Long) o; - } - if (o == Null.INSTANCE) { - return Double.NaN; - } - if (o == Undefined.INSTANCE) { - return Double.NaN; - } - if (o instanceof Boolean) { - return (Boolean) o ? 1.0 : 0.0; - } - if (o instanceof String) { - try { - return Double.parseDouble((String) o); - } catch (NumberFormatException nfe) { - return Double.NaN; - } - } - return 0; - } - - public static GraphTargetItem gettoset(GraphTargetItem get, GraphTargetItem value, List variables) { - GraphTargetItem ret = get; - boolean boxed = false; - if (get instanceof VariableActionItem) { - boxed = true; - ret = ((VariableActionItem) ret).getBoxedValue(); - } - if (ret instanceof GetVariableActionItem) { - GetVariableActionItem gv = (GetVariableActionItem) ret; - ret = new SetVariableActionItem(null, null, gv.name, value); - } else if (ret instanceof GetMemberActionItem) { - GetMemberActionItem mem = (GetMemberActionItem) ret; - ret = new SetMemberActionItem(null, null, mem.object, mem.memberName, value); - } else if ((ret instanceof DirectValueActionItem) && ((DirectValueActionItem) ret).value instanceof RegisterNumber) { - ret = new StoreRegisterActionItem(null, null, (RegisterNumber) ((DirectValueActionItem) ret).value, value, false); - } else if (ret instanceof GetPropertyActionItem) { - GetPropertyActionItem gp = (GetPropertyActionItem) ret; - ret = new SetPropertyActionItem(null, null, gp.target, gp.propertyIndex, value); - } - if (boxed) { - GraphTargetItem b = ret; - ret = new VariableActionItem(((VariableActionItem) get).getVariableName(), value, ((VariableActionItem) get).isDefinition()); - ((VariableActionItem) ret).setBoxedValue((ActionItem) b); - variables.remove((VariableActionItem) get); - variables.add((VariableActionItem) ret); - } - return ret; - } - - @Override - public boolean isDeobfuscatePop() { - return false; - } - - @Override - public int getLine() { - return 0; - } - - @Override - public String getFile() { - return null; - } -} +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.action; + +import com.jpexs.decompiler.flash.AppResources; +import com.jpexs.decompiler.flash.BaseLocalData; +import com.jpexs.decompiler.flash.DisassemblyListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.abc.avm2.parser.script.Reference; +import com.jpexs.decompiler.flash.action.deobfuscation.ActionDeobfuscator; +import com.jpexs.decompiler.flash.action.model.ActionItem; +import com.jpexs.decompiler.flash.action.model.ConstantPool; +import com.jpexs.decompiler.flash.action.model.DirectValueActionItem; +import com.jpexs.decompiler.flash.action.model.ExtendsActionItem; +import com.jpexs.decompiler.flash.action.model.FunctionActionItem; +import com.jpexs.decompiler.flash.action.model.GetMemberActionItem; +import com.jpexs.decompiler.flash.action.model.GetPropertyActionItem; +import com.jpexs.decompiler.flash.action.model.GetVariableActionItem; +import com.jpexs.decompiler.flash.action.model.ImplementsOpActionItem; +import com.jpexs.decompiler.flash.action.model.NewObjectActionItem; +import com.jpexs.decompiler.flash.action.model.SetMemberActionItem; +import com.jpexs.decompiler.flash.action.model.SetPropertyActionItem; +import com.jpexs.decompiler.flash.action.model.SetVariableActionItem; +import com.jpexs.decompiler.flash.action.model.StoreRegisterActionItem; +import com.jpexs.decompiler.flash.action.model.TemporaryRegister; +import com.jpexs.decompiler.flash.action.model.clauses.ClassActionItem; +import com.jpexs.decompiler.flash.action.model.clauses.InterfaceActionItem; +import com.jpexs.decompiler.flash.action.parser.ActionParseException; +import com.jpexs.decompiler.flash.action.parser.pcode.ASMParsedSymbol; +import com.jpexs.decompiler.flash.action.parser.pcode.FlasmLexer; +import com.jpexs.decompiler.flash.action.parser.script.VariableActionItem; +import com.jpexs.decompiler.flash.action.special.ActionEnd; +import com.jpexs.decompiler.flash.action.special.ActionStore; +import com.jpexs.decompiler.flash.action.swf4.ActionEquals; +import com.jpexs.decompiler.flash.action.swf4.ActionIf; +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.ActionConstantPool; +import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction; +import com.jpexs.decompiler.flash.action.swf5.ActionEquals2; +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.configuration.Configuration; +import com.jpexs.decompiler.flash.ecma.EcmaScript; +import com.jpexs.decompiler.flash.ecma.Null; +import com.jpexs.decompiler.flash.ecma.Undefined; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.helpers.CodeFormatting; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; +import com.jpexs.decompiler.flash.helpers.NulWriter; +import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; +import com.jpexs.decompiler.flash.helpers.collections.MyEntry; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.graph.Graph; +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.TranslateException; +import com.jpexs.decompiler.graph.TranslateStack; +import com.jpexs.decompiler.graph.model.CommentItem; +import com.jpexs.decompiler.graph.model.IfItem; +import com.jpexs.decompiler.graph.model.LocalData; +import com.jpexs.decompiler.graph.model.NotItem; +import com.jpexs.decompiler.graph.model.PopItem; +import com.jpexs.decompiler.graph.model.ScriptEndItem; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.CancellableWorker; +import com.jpexs.helpers.Helper; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Represents one ACTIONRECORD, also has some static method to work with Actions + * + * @author JPEXS + */ +public abstract class Action implements GraphSourceItem { + + private static final int INFORM_LISTENER_RESOLUTION = 100; + + private boolean ignored = false; + + public long fileOffset = -1; + + /** + * Action type identifier + */ + private int actionCode; + + /** + * Length of action data + */ + protected int actionLength; + + private long address; + + @Override + public long getLineOffset() { + return fileOffset; + } + + /** + * Names of ActionScript properties + */ + public static final String[] propertyNames = new String[]{ + "_X", + "_Y", + "_xscale", + "_yscale", + "_currentframe", + "_totalframes", + "_alpha", + "_visible", + "_width", + "_height", + "_rotation", + "_target", + "_framesloaded", + "_name", + "_droptarget", + "_url", + "_highquality", + "_focusrect", + "_soundbuftime", + "_quality", + "_xmouse", + "_ymouse" + }; + + public static final List propertyNamesList = Arrays.asList(propertyNames); + + private static final Logger logger = Logger.getLogger(Action.class.getName()); + + /** + * Constructor + * + * @param actionCode Action type identifier + * @param actionLength Length of action data + */ + public Action(int actionCode, int actionLength) { + this.actionCode = actionCode; + this.actionLength = actionLength; + } + + public Action() { + } + + /** + * Returns address of this action + * + * @return address of this action + */ + @Override + public long getAddress() { + return address; + } + + /** + * Return code of this action + * + * @return code of this action + */ + public int getActionCode() { + return actionCode; + } + + /** + * Gets all addresses which are referenced from this action and/or + * subactions + * + * @param refs list of addresses + */ + public void getRef(Set refs) { + } + + /** + * Gets all addresses which are referenced from the list of actions + * + * @param list List of actions + * @return List of addresses + */ + public static Set getActionsAllRefs(List list) { + Set ret = new HashSet<>(); + for (Action a : list) { + a.getRef(ret); + } + return ret; + } + + public int getTotalActionLength() { + return actionLength + 1 + (actionCode >= 0x80 ? 2 : 0); + } + + /** + * Sets address of this instruction + * + * @param address Address + */ + public void setAddress(long address) { + this.address = address; + } + + /** + * Returns a string representation of the object + * + * @return a string representation of the object. + */ + @Override + public String toString() { + return "Action" + actionCode; + } + + /** + * Reads String from FlasmLexer + * + * @param lex FlasmLexer + * @return String value + * @throws IOException + * @throws ActionParseException When read object is not String + */ + protected String lexString(FlasmLexer lex) throws IOException, ActionParseException { + ASMParsedSymbol symb = lex.yylex(); + if (symb.type != ASMParsedSymbol.TYPE_STRING) { + throw new ActionParseException("String expected", lex.yyline()); + } + return (String) symb.value; + } + + /** + * Reads Block startServer from FlasmLexer + * + * @param lex FlasmLexer + * @throws IOException + * @throws ActionParseException When read object is not Block startServer + */ + protected void lexBlockOpen(FlasmLexer lex) throws IOException, ActionParseException { + ASMParsedSymbol symb = lex.yylex(); + if (symb.type != ASMParsedSymbol.TYPE_BLOCK_START) { + throw new ActionParseException("Block startServer ", lex.yyline()); + } + } + + /** + * Reads Identifier from FlasmLexer + * + * @param lex FlasmLexer + * @return Identifier name + * @throws IOException + * @throws ActionParseException When read object is not Identifier + */ + protected String lexIdentifier(FlasmLexer lex) throws IOException, ActionParseException { + ASMParsedSymbol symb = lex.yylex(); + if (symb.type != ASMParsedSymbol.TYPE_IDENTIFIER) { + throw new ActionParseException("Identifier expected", lex.yyline()); + } + return (String) symb.value; + } + + /** + * Reads long value from FlasmLexer + * + * @param lex FlasmLexer + * @return long value + * @throws IOException + * @throws ActionParseException When read object is not long value + */ + protected long lexLong(FlasmLexer lex) throws IOException, ActionParseException { + ASMParsedSymbol symb = lex.yylex(); + if (symb.type != ASMParsedSymbol.TYPE_INTEGER) { + throw new ActionParseException("Integer expected", lex.yyline()); + } + return (Long) symb.value; + } + + /** + * Reads boolean value from FlasmLexer + * + * @param lex FlasmLexer + * @return boolean value + * @throws IOException + * @throws ActionParseException When read object is not boolean value + */ + protected boolean lexBoolean(FlasmLexer lex) throws IOException, ActionParseException { + ASMParsedSymbol symb = lex.yylex(); + if (symb.type != ASMParsedSymbol.TYPE_BOOLEAN) { + throw new ActionParseException("Boolean expected", lex.yyline()); + } + return (Boolean) symb.value; + } + + /** + * Gets action converted to bytes + * + * @param version SWF version + * @return Array of bytes + */ + public final byte[] getBytes(int version) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(baos, version); + try { + getContentBytes(sos); + sos.close(); + } catch (IOException e) { + throw new Error("This should never happen.", e); + } + return surroundWithAction(baos.toByteArray(), version); + } + + protected void getContentBytes(SWFOutputStream sos) throws IOException { + } + + /** + * Gets the length of action converted to bytes + * + * @return Length + */ + public final int getBytesLength() { + return getContentBytesLength() + (actionCode >= 0x80 ? 3 : 1); + } + + protected int getContentBytesLength() { + return 0; + } + + /** + * Updates the action length to the length calculated from action bytes + */ + public void updateLength() { + int length = getBytesLength(); + actionLength = length - 1 - (actionCode >= 0x80 ? 2 : 0); + } + + /** + * Surrounds byte array with Action header + * + * @param data Byte array + * @param version SWF version + * @return Byte array + */ + private byte[] surroundWithAction(byte[] data, int version) { + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + SWFOutputStream sos2 = new SWFOutputStream(baos2, version); + try { + sos2.writeUI8(actionCode); + if (actionCode >= 0x80) { + sos2.writeUI16(data.length); + } + sos2.write(data); + sos2.close(); + } catch (IOException e) { + throw new Error("This should never happen.", e); + } + return baos2.toByteArray(); + } + + @Override + public long getFileOffset() { + return fileOffset; + } + + /** + * Converts list of Actions to bytes + * + * @param list List of actions + * @param addZero Whether or not to add 0 UI8 value to the end + * @param version SWF version + * @return Array of bytes + */ + public static byte[] actionsToBytes(List list, boolean addZero, int version) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Action lastAction = null; + for (Action a : list) { + try { + lastAction = a; + baos.write(a.getBytes(version)); + } catch (IOException e) { + } + } + if (addZero && (lastAction == null || !(lastAction instanceof ActionEnd))) { + baos.write(0); + } + return baos.toByteArray(); + } + + public static ByteArrayRange actionsToByteArrayRange(List list, boolean addZero, int version) { + byte[] bytes = Action.actionsToBytes(list, addZero, version); + return new ByteArrayRange(bytes); + } + + public static void setConstantPools(ASMSource src, List> constantPools, boolean tryInline) throws ConstantPoolTooBigException { + try { + ActionList actions = src.getActions(); + int poolIdx = 0; + for (Action action : actions) { + if (action instanceof ActionConstantPool) { + ActionConstantPool cPool = (ActionConstantPool) action; + List constantPool = constantPools.get(poolIdx); + + int size = ActionConstantPool.calculateSize(constantPool); + if (size > 0xffff && tryInline) { + for (int i = 0; i < constantPool.size(); i++) { + int refCount = actions.getConstantPoolIndexReferenceCount(i); + if (refCount == 1) { + actions.inlineConstantPoolString(i, constantPool.get(i)); + constantPool.set(i, ""); + } + } + + size = ActionConstantPool.calculateSize(constantPool); + } + + if (size > 0xffff) { + throw new ConstantPoolTooBigException(poolIdx, size); + } + + cPool.constantPool = constantPool; + + poolIdx++; + if (constantPools.size() <= poolIdx) { + break; + } + } + } + + actions.removeNonReferencedConstantPoolItems(); + src.setActions(actions); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + + /** + * Set addresses of actions in the list + * + * @param list List of actions + * @param baseAddress Address of first action in the list + */ + public static void setActionsAddresses(List list, long baseAddress) { + long offset = baseAddress; + for (Action a : list) { + a.setAddress(offset); + offset += a.getTotalActionLength(); + } + } + + /** + * Converts list of actions to ASM source + * + * @param listeners + * @param address + * @param list List of actions + * @param version SWF version + * @param exportMode PCode or hex? + * @return source ASM + * + */ + public static String actionsToString(List listeners, long address, ActionList list, int version, ScriptExportMode exportMode) { + HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); + actionsToString(listeners, address, list, version, exportMode, writer); + return writer.toString(); + } + + private static void informListeners(List listeners, int pos, int count) { + if (pos % INFORM_LISTENER_RESOLUTION == 0) { + DisassemblyListener[] listenersArray = listeners.toArray(new DisassemblyListener[listeners.size()]); + for (DisassemblyListener listener : listenersArray) { + listener.progressToString(pos + 1, count); + } + } + } + + /** + * Converts list of actions to ASM source + * + * @param listeners + * @param address + * @param list List of actions + * @param version SWF version + * @param exportMode PCode or hex? + * @param writer + * @return GraphTextWriter + */ + public static GraphTextWriter actionsToString(List listeners, long address, ActionList list, int version, ScriptExportMode exportMode, GraphTextWriter writer) { + if (exportMode == ScriptExportMode.CONSTANTS) { + return constantPoolActionsToString(listeners, address, list, version, exportMode, writer); + } + + long offset; + Set importantOffsets = getActionsAllRefs(list); + /*List cps = SWFInputStream.getConstantPool(new ArrayList(), new ActionGraphSource(list, version, new HashMap(), new HashMap(), new HashMap()), 0, version, path); + if (!cps.isEmpty()) { + setConstantPool(list, cps.get(cps.size() - 1)); + }*/ + HashMap> containers = new HashMap<>(); + HashMap containersPos = new HashMap<>(); + offset = address; + int pos = 0; + boolean lastPush = false; + byte[] fileData = list.fileData; + for (Action a : list) { + informListeners(listeners, pos, list.size()); + + if (exportMode == ScriptExportMode.PCODE_HEX) { + if (lastPush) { + writer.newLine(); + lastPush = false; + } + writer.appendNoHilight("; "); + long fileOffset = a.getFileOffset(); + if (Configuration.showFileOffsetInPcodeHex.get()) { + writer.appendNoHilight("@"); + writer.appendNoHilight(Helper.formatHex(fileOffset, 8)); + writer.appendNoHilight(" "); + } + + byte[] bytes = a.getBytes(version); + writer.appendNoHilight(Helper.bytesToHexString(bytes)); + + if (Configuration.showOriginalBytesInPcodeHex.get()) { + if (fileData != null && fileOffset != -1 && fileData.length > fileOffset + bytes.length - 1) { + boolean same = true; + for (int i = 0; i < bytes.length; i++) { + byte b = fileData[(int) (fileOffset + i)]; + if (b != bytes[i]) { + same = false; + break; + } + } + + if (!same) { + writer.appendNoHilight(" ("); + for (int i = 0; i < bytes.length; i++) { + if (i != 0) { + writer.appendNoHilight(" "); + } + + writer.appendNoHilight(Helper.byteToHex(fileData[(int) (fileOffset + i)])); + } + + writer.appendNoHilight(")"); + } + + } + } + + writer.newLine(); + } + + offset = a.getAddress(); + + if ((!(a.isIgnored())) && (a instanceof GraphSourceItemContainer)) { + GraphSourceItemContainer cnt = (GraphSourceItemContainer) a; + containersPos.put(cnt, 0); + List sizes = cnt.getContainerSizes(); + long addr = ((Action) cnt).getAddress() + cnt.getHeaderSize(); + for (Long size : sizes) { + addr += size; + if (size == 0) { + continue; + } + if (!containers.containsKey(addr)) { + containers.put(addr, new ArrayList<>()); + } + containers.get(addr).add(cnt); + } + } + + if (containers.containsKey(offset)) { + for (int i = 0; i < containers.get(offset).size(); i++) { + if (lastPush) { + writer.newLine(); + lastPush = false; + } + writer.appendNoHilight("}").newLine(); + GraphSourceItemContainer cnt = containers.get(offset).get(i); + int cntPos = containersPos.get(cnt); + writer.appendNoHilight(cnt.getASMSourceBetween(cntPos)); + cntPos++; + containersPos.put(cnt, cntPos); + } + } + + if (Configuration.showAllAddresses.get() || importantOffsets.contains(offset)) { + if (lastPush) { + writer.newLine(); + lastPush = false; + } + writer.appendNoHilight("loc"); + writer.appendNoHilight(Helper.formatAddress(offset)); + writer.appendNoHilight(":"); + } + + if (a.isIgnored()) { + if (lastPush) { + writer.newLine(); + lastPush = false; + } + if (!(a instanceof ActionEnd)) { + int len = a.getTotalActionLength(); + for (int i = 0; i < len; i++) { + writer.appendNoHilight("Nop").newLine(); + } + } + } else { + //if (!(a instanceof ActionNop)) { + String add = ""; + // honfika: commented out the following lines, because it makes no sense + /*if (a instanceof ActionIf) { + add = " change: " + ((ActionIf) a).getJumpOffset(); + } + if (a instanceof ActionJump) { + add = " change: " + ((ActionJump) a).getJumpOffset(); + } + add = "; ofs" + Helper.formatAddress(offset) + add; + add = "";*/ + if ((a instanceof ActionPush) && lastPush) { + writer.appendNoHilight(" "); + ((ActionPush) a).paramsToStringReplaced(list, importantOffsets, exportMode, writer); + } else { + if (lastPush) { + writer.newLine(); + //lastPush = false; + } + + writer.append("", offset, a.getFileOffset()); + + int fixBranch = -1; + if (a instanceof ActionIf) { + ActionIf aif = (ActionIf) a; + if (aif.jumpUsed && !aif.ignoreUsed) { + fixBranch = 0; + } + if (!aif.jumpUsed && aif.ignoreUsed) { + fixBranch = 1; + } + } + + if (fixBranch > -1) { + writer.appendNoHilight("FFDec_DeobfuscatePop"); + if (fixBranch == 0) { //jump + writer.newLine(); + writer.appendNoHilight("Jump loc"); + writer.appendNoHilight(Helper.formatAddress(((ActionIf) a).getTargetAddress())); + } else { + //nojump, ignore + } + } else { + a.getASMSourceReplaced(list, importantOffsets, exportMode, writer); + } + writer.appendNoHilight(a.isIgnored() ? "; ignored" : ""); + writer.appendNoHilight(add); + if (!(a instanceof ActionPush)) { + writer.newLine(); + } + } + lastPush = a instanceof ActionPush; + //} + } + + offset += a.getTotalActionLength(); + pos++; + } + if (lastPush) { + writer.newLine(); + } + + if (containers.containsKey(offset)) { + for (int i = 0; i < containers.get(offset).size(); i++) { + writer.appendNoHilight("}"); + writer.newLine(); + GraphSourceItemContainer cnt = containers.get(offset).get(i); + int cntPos = containersPos.get(cnt); + writer.appendNoHilight(cnt.getASMSourceBetween(cntPos)); + cntPos++; + containersPos.put(cnt, cntPos); + } + } + + if (importantOffsets.contains(offset)) { + writer.appendNoHilight("loc"); + writer.appendNoHilight(Helper.formatAddress(offset)); + writer.appendNoHilight(":"); + writer.newLine(); + } + + return writer; + } + + public static GraphTextWriter constantPoolActionsToString(List listeners, long address, ActionList list, int version, ScriptExportMode exportMode, GraphTextWriter writer) { + int poolIdx = 0; + writer.appendNoHilight(Helper.constants).newLine(); + for (Action a : list) { + if (a instanceof ActionConstantPool) { + if (poolIdx > 0) { + writer.appendNoHilight("---").newLine(); + } + + ActionConstantPool cPool = (ActionConstantPool) a; + int constIdx = 0; + for (String c : cPool.constantPool) { + writer.appendNoHilight(constIdx); + writer.appendNoHilight("|"); + writer.appendNoHilight(Helper.escapeString(c)); + writer.newLine(); + constIdx++; + } + + poolIdx++; + } + } + + return writer; + } + + /** + * Convert action to ASM source + * + * @param container + * @param knownAddreses List of important offsets to mark as labels + * @param exportMode PCode or hex? + * @return String of P-code source + */ + public String getASMSource(ActionList container, Set knownAddreses, ScriptExportMode exportMode) { + return toString(); + } + + public abstract boolean execute(LocalDataArea lda); + + /* { + //throw new UnsupportedOperationException("Action " + toString() + " not implemented"); + return false; + }*/ + /** + * Translates this function to stack and output. + * + * @param lineStartIns Line start instruction + * @param stack Stack + * @param output Output + * @param regNames Register names + * @param variables Variables + * @param functions Functions + * @param staticOperation the value of staticOperation + * @param path the value of path + * @throws java.lang.InterruptedException + */ + public void translate(GraphSourceItem lineStartIns, TranslateStack stack, List output, HashMap regNames, HashMap variables, HashMap functions, int staticOperation, String path) throws InterruptedException { + } + + @Override + public int getStackPopCount(BaseLocalData localData, TranslateStack stack) { + return 0; + } + + @Override + public int getStackPushCount(BaseLocalData localData, TranslateStack stack) { + return 0; + } + + /** + * Pops long value off the stack + * + * @param stack Stack + * @return long value + */ + protected long popLong(TranslateStack stack) { + GraphTargetItem item = stack.pop(); + if (item instanceof DirectValueActionItem) { + return (long) (double) EcmaScript.toNumberAs2(((DirectValueActionItem) item).value); + } + + return 0; + } + + /** + * Converts action index to address in the specified list of actions + * + * @param actions List of actions + * @param ip Action index + * @return address + */ + public static long ip2adr(List actions, int ip) { + /* List actions=new ArrayList(); + for(GraphSourceItem s:sources){ + if(s instanceof Action){ + actions.add((Action)s); + } + }*/ + if (ip >= actions.size()) { + if (actions.isEmpty()) { + return 0; + } + return actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getTotalActionLength(); + } + if (ip == -1) { + return 0; + } + return actions.get(ip).getAddress(); + } + + /** + * Converts address to action index in the specified list of actions + * + * @param actions List of actions + * @param addr Address + * @return action index + */ + public static int adr2ip(List actions, long addr) { + for (int ip = 0; ip < actions.size(); ip++) { + if (actions.get(ip).getAddress() == addr) { + return ip; + } + } + if (actions.size() > 0) { + long outpos = actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getTotalActionLength(); + if (addr == outpos) { + return actions.size(); + } + } + return -1; + } + + public static List actionsToTree(List actions, int version, int staticOperation, String path) throws InterruptedException { + return actionsToTree(new HashMap<>(), new HashMap<>(), new HashMap<>(), actions, version, staticOperation, path); + } + + /** + * Converts list of actions to ActionScript source code + * + * @param asm + * @param actions List of actions + * @param path + * @return source + * @throws java.lang.InterruptedException + */ + public static String actionsToSource(final ASMSource asm, final List actions, final String path) throws InterruptedException { + HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); + actionsToSource(asm, actions, path, writer); + return writer.toString(); + } + + /** + * Converts list of actions to ActionScript source code + * + * @param asm + * @param actions List of actions + * @param path + * @param writer + * @throws java.lang.InterruptedException + */ + public static void actionsToSource(final ASMSource asm, final List actions, final String path, GraphTextWriter writer) throws InterruptedException { + writer.suspendMeasure(); + List tree = null; + Throwable convertException = null; + int timeout = Configuration.decompilationTimeoutSingleMethod.get(); + final SWF swf = asm == null ? null : asm.getSwf(); + final int version = swf == null ? SWF.DEFAULT_VERSION : swf.version; + try { + tree = CancellableWorker.call(new Callable>() { + @Override + public List call() throws Exception { + int staticOperation = Graph.SOP_USE_STATIC; //(Boolean) Configuration.getConfig("autoDeobfuscate", true) ? Graph.SOP_SKIP_STATIC : Graph.SOP_USE_STATIC; + List tree = actionsToTree(new HashMap<>(), new HashMap<>(), new HashMap<>(), actions, version, staticOperation, path); + SWFDecompilerPlugin.fireActionTreeCreated(tree, swf); + if (Configuration.autoDeobfuscate.get()) { + new ActionDeobfuscator().actionTreeCreated(tree, swf); + } + + Graph.graphToString(tree, new NulWriter(), new LocalData()); + return tree; + } + }, timeout, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + throw ex; + } catch (Exception | OutOfMemoryError | StackOverflowError ex) { + + convertException = ex; + Throwable cause = ex.getCause(); + if (ex instanceof ExecutionException && cause instanceof Exception) { + convertException = cause; + } + if (convertException instanceof TimeoutException) { + logger.log(Level.SEVERE, "Decompilation timeout in: " + path, convertException); + } else { + logger.log(Level.SEVERE, "Decompilation error in: " + path, convertException); + } + + } + writer.continueMeasure(); + + if (asm != null) { + asm.getActionSourcePrefix(writer); + } + if (convertException == null) { + Graph.graphToString(tree, writer, new LocalData()); + } else if (convertException instanceof TimeoutException) { + Helper.appendTimeoutCommentAs2(writer, timeout, actions.size()); + } else { + Helper.appendErrorComment(writer, convertException); + } + if (asm != null) { + asm.getActionSourceSuffix(writer); + } + } + + /** + * Converts list of actions to List of treeItems + * + * @param regNames Register names + * @param variables + * @param functions + * @param actions List of actions + * @param version SWF version + * @param staticOperation + * @param path + * @return List of treeItems + * @throws java.lang.InterruptedException + */ + public static List actionsToTree(HashMap regNames, HashMap variables, HashMap functions, List actions, int version, int staticOperation, String path) throws InterruptedException { + return ActionGraph.translateViaGraph(regNames, variables, functions, actions, version, staticOperation, path); + } + + @Override + public void translate(BaseLocalData localData, TranslateStack stack, List output, int staticOperation, String path) throws InterruptedException { + ActionLocalData aLocalData = (ActionLocalData) localData; + /*int expectedSize = stack.size() - getStackPopCount(localData, stack); + if (expectedSize < 0) { + expectedSize = 0; + } + expectedSize += getStackPushCount(localData, stack);*/ + + translate(aLocalData.lineStartAction, stack, output, aLocalData.regNames, aLocalData.variables, aLocalData.functions, staticOperation, path); + /*if (stack.size() != expectedSize && !(this instanceof ActionPushDuplicate)) { + throw new Error("HONFIKA stack size mismatch"); + }*/ + } + + @Override + public boolean isJump() { + return false; + } + + @Override + public boolean isBranch() { + return false; + } + + @Override + public boolean isExit() { + return false; + } + + @Override + public List getBranches(GraphSource code) { + return new ArrayList<>(); + } + + @Override + public boolean isIgnored() { + return ignored; + } + + @Override + public void setIgnored(boolean ignored, int pos) { + this.ignored = ignored; + } + + public static List actionsPartToTree(Reference fi, HashMap registerNames, HashMap variables, HashMap functions, TranslateStack stack, List actions, int start, int end, int version, int staticOperation, String path) throws InterruptedException { + if (start < actions.size() && (end > 0) && (start > 0)) { + logger.log(Level.FINE, "Entering {0}-{1}{2}", new Object[]{start, end, actions.size() > 0 ? (" (" + actions.get(start).toString() + " - " + actions.get(end == actions.size() ? end - 1 : end) + ")") : ""}); + } + ActionLocalData localData = new ActionLocalData(registerNames, variables, functions); + localData.lineStartAction = fi.getVal(); + List output = new ArrayList<>(); + int ip = start; + boolean isWhile = false; + boolean isForIn = false; + GraphTargetItem inItem = null; + int loopStart = 0; + loopip: + while (ip <= end) { + + long addr = ip2adr(actions, ip); + if (ip > end) { + break; + } + if (ip >= actions.size()) { + output.add(new ScriptEndItem()); + break; + } + if (Configuration.simplifyExpressions.get()) { + stack.simplify(); + } + Action action = actions.get(ip); + if (action.isIgnored()) { + ip++; + continue; + } + + //FunctionActionItem after DefineFunction(/2) are left on the stack. For linestart offsets we consider this kind of stack empty. + boolean isStackEmpty = true; + for (int i = 0; i < stack.size(); i++) { + if ((!(stack.get(i) instanceof FunctionActionItem))) { + isStackEmpty = false; + break; + } + } + + if (isStackEmpty) { + localData.lineStartAction = action; + fi.setVal(action); + } + if (action instanceof GraphSourceItemContainer) { + GraphSourceItemContainer cnt = (GraphSourceItemContainer) action; + //List out=actionsPartToTree(new HashMap(), new HashMap(),new HashMap(), new TranslateStack(), src, ip+1,endip-1 , version); + long endAddr = action.getAddress() + cnt.getHeaderSize(); + String cntName = cnt.getName(); + List> outs = new ArrayList<>(); + HashMap variables2 = Helper.deepCopy(variables); + if (cnt instanceof ActionDefineFunction || cnt instanceof ActionDefineFunction2) { + for (int r = 0; r < 256; r++) { + if (variables2.containsKey("__register" + r)) { + variables2.remove("__register" + r); + } + } + } + for (long size : cnt.getContainerSizes()) { + if (size == 0) { + outs.add(new ArrayList<>()); + continue; + } + List out; + try { + HashMap regNames = cnt.getRegNames(); + if (action instanceof ActionWith || action instanceof ActionTry) { + for (Map.Entry e : registerNames.entrySet()) { + if (!regNames.containsKey(e.getKey())) { + regNames.put(e.getKey(), e.getValue()); + } + } + } + out = ActionGraph.translateViaGraph(regNames, variables2, functions, actions.subList(adr2ip(actions, endAddr), adr2ip(actions, endAddr + size)), version, staticOperation, path + (cntName == null ? "" : "/" + cntName)); + } catch (OutOfMemoryError | TranslateException | StackOverflowError ex) { + logger.log(Level.SEVERE, "Decompilation error in: " + path, ex); + if (ex instanceof OutOfMemoryError) { + Helper.freeMem(); + } + + out = new ArrayList<>(); + out.add(new CommentItem(new String[]{ + "", + " * " + AppResources.translate("decompilationError"), + " * " + AppResources.translate("decompilationError.obfuscated"), + Helper.decompilationErrorAdd == null ? null : " * " + Helper.decompilationErrorAdd, + " * " + AppResources.translate("decompilationError.errorType") + ": " + + ex.getClass().getSimpleName(), + ""})); + } + outs.add(out); + endAddr += size; + } + + ((GraphSourceItemContainer) action).translateContainer(outs, action, stack, output, registerNames, variables, functions); + ip = adr2ip(actions, endAddr); + continue; + } + + //return in for..in + if ((action instanceof ActionPush) && (((ActionPush) action).values.size() == 1) && (((ActionPush) action).values.get(0) == Null.INSTANCE)) { + if (ip + 3 <= end) { + if ((actions.get(ip + 1) instanceof ActionEquals) || (actions.get(ip + 1) instanceof ActionEquals2)) { + if (actions.get(ip + 2) instanceof ActionNot) { + if (actions.get(ip + 3) instanceof ActionIf) { + ActionIf aif = (ActionIf) actions.get(ip + 3); + if (adr2ip(actions, ip2adr(actions, ip + 4) + aif.getJumpOffset()) == ip) { + ip += 4; + continue; + } + } + } + } + } + } + + /*ActionJump && ActionIf removed*/ + /*if ((action instanceof ActionEnumerate2) || (action instanceof ActionEnumerate)) { + loopStart = ip + 1; + isForIn = true; + ip += 4; + action.translate(localData, stack, output); + EnumerateActionItem en = (EnumerateActionItem) stack.peek(); + inItem = en.object; + continue; + } else*/ /*if (action instanceof ActionTry) { + ActionTry atry = (ActionTry) action; + List tryCommands = ActionGraph.translateViaGraph(registerNames, variables, functions, atry.tryBody, version); + ActionItem catchName; + if (atry.catchInRegisterFlag) { + catchName = new DirectValueActionItem(atry, -1, new RegisterNumber(atry.catchRegister), new ArrayList<>()); + } else { + catchName = new DirectValueActionItem(atry, -1, atry.catchName, new ArrayList<>()); + } + List catchExceptions = new ArrayList(); + catchExceptions.add(catchName); + List> catchCommands = new ArrayList>(); + catchCommands.add(ActionGraph.translateViaGraph(registerNames, variables, functions, atry.catchBody, version)); + List finallyCommands = ActionGraph.translateViaGraph(registerNames, variables, functions, atry.finallyBody, version); + output.add(new TryActionItem(tryCommands, catchExceptions, catchCommands, finallyCommands)); + } else if (action instanceof ActionWith) { + ActionWith awith = (ActionWith) action; + List withCommands = ActionGraph.translateViaGraph(registerNames, variables, functions,new ArrayList() , version); //TODO:parse with actions + output.add(new WithActionItem(action, stack.pop(), withCommands)); + } else */ if (false) { + } /*if (action instanceof ActionStoreRegister) { + if ((ip + 1 <= end) && (actions.get(ip + 1) instanceof ActionPop)) { + action.translate(localData, stack, output); + stack.pop(); + ip++; + } else { + try { + action.translate(localData, stack, output); + } catch (Exception ex) { + // ignore + } + } + } */ /*else if (action instanceof ActionStrictEquals) { + if ((ip + 1 < actions.size()) && (actions.get(ip + 1) instanceof ActionIf)) { + List caseValues = new ArrayList(); + List> caseCommands = new ArrayList>(); + caseValues.add(stack.pop()); + ActionItem switchedObject = stack.pop(); + if (output.size() > 0) { + if (output.get(output.size() - 1) instanceof StoreRegisterActionItem) { + output.remove(output.size() - 1); + } + } + int caseStart = ip + 2; + List caseBodyIps = new ArrayList(); + long defaultAddr = 0; + caseBodyIps.add(adr2ip(actions, ((ActionIf) actions.get(ip + 1)).getRef(version), version)); + ip++; + do { + ip++; + if ((actions.get(ip - 1) instanceof ActionStrictEquals) && (actions.get(ip) instanceof ActionIf)) { + caseValues.add(actionsToStackTree(registerNames, jumpsOrIfs, actions, constants, caseStart, ip - 2, version).pop()); + caseStart = ip + 1; + caseBodyIps.add(adr2ip(actions, ((ActionIf) actions.get(ip)).getRef(version), version)); + if (actions.get(ip + 1) instanceof ActionJump) { + defaultAddr = ((ActionJump) actions.get(ip + 1)).getRef(version); + ip = adr2ip(actions, defaultAddr, version); + break; + } + } + } while (ip < end); + + for (int i = 0; i < caseBodyIps.size(); i++) { + int caseEnd = ip - 1; + if (i < caseBodyIps.size() - 1) { + caseEnd = caseBodyIps.get(i + 1) - 1; + } + caseCommands.add(actionsToTree(registerNames, unknownJumps, loopList, jumpsOrIfs, stack, constants, actions, caseBodyIps.get(i), caseEnd, version)); + } + output.add(new SwitchActionItem(action, defaultAddr, switchedObject, caseValues, caseCommands, null)); + continue; + } else { + action.translate(stack, constants, output, registerNames); + } + } */ else { + + if (action instanceof ActionStore) { + ActionStore store = (ActionStore) action; + store.setStore(actions.subList(ip + 1, ip + 1 + store.getStoreSize())); + ip = ip + 1 + store.getStoreSize() - 1/*ip++ will be next*/; + } + + action.translate(localData, stack, output, staticOperation, path); + } + + ip++; + } + //output = checkClass(output); + logger.log(Level.FINE, "Leaving {0}-{1}", new Object[]{start, end}); + return output; + } + + public static GraphTargetItem getWithoutGlobal(GraphTargetItem ti) { + GraphTargetItem t = ti; + if (!(t instanceof GetMemberActionItem)) { + return ti; + } + GetMemberActionItem lastMember = null; + while (((GetMemberActionItem) t).object instanceof GetMemberActionItem) { + lastMember = (GetMemberActionItem) t; + t = ((GetMemberActionItem) t).object; + } + if (((GetMemberActionItem) t).object instanceof GetVariableActionItem) { + GetVariableActionItem v = (GetVariableActionItem) ((GetMemberActionItem) t).object; + if (v.name instanceof DirectValueActionItem) { + if (((DirectValueActionItem) v.name).value instanceof String) { + if (((DirectValueActionItem) v.name).value.equals("_global")) { + GetVariableActionItem gvt = new GetVariableActionItem(null, null, ((GetMemberActionItem) t).memberName); + if (lastMember == null) { + return gvt; + } else { + lastMember.object = gvt; + } + } + } + } + } + return ti; + } + + public static List checkClass(List output) { + if (true) { + //return output; + } + List ret = new ArrayList<>(); + List functions = new ArrayList<>(); + List staticFunctions = new ArrayList<>(); + List> vars = new ArrayList<>(); + List> staticVars = new ArrayList<>(); + GraphTargetItem className; + GraphTargetItem extendsOp = null; + List implementsOp = new ArrayList<>(); + boolean ok = true; + int prevCount = 0; + for (GraphTargetItem t : output) { + if (t instanceof IfItem) { + IfItem it = (IfItem) t; + if (it.expression instanceof NotItem) { + NotItem nti = (NotItem) it.expression; + if ((nti.value instanceof GetMemberActionItem) || (nti.value instanceof GetVariableActionItem)) { + if (true) { //it.onFalse.isEmpty()){ //||(it.onFalse.get(0) instanceof UnsupportedActionItem)) { + if ((it.onTrue.size() == 1) && (it.onTrue.get(0) instanceof SetMemberActionItem) && (((SetMemberActionItem) it.onTrue.get(0)).value instanceof NewObjectActionItem)) { + // ignore + } else { + List parts = it.onTrue; + className = getWithoutGlobal(nti.value); + if (parts.size() >= 1) { + int ipos = 0; + + while ((parts.get(ipos) instanceof PopItem) || ((parts.get(ipos) instanceof IfItem) && ((((IfItem) parts.get(ipos)).onTrue.size() == 1) && (((IfItem) parts.get(ipos)).onTrue.get(0) instanceof SetMemberActionItem) && (((SetMemberActionItem) ((IfItem) parts.get(ipos)).onTrue.get(0)).value instanceof NewObjectActionItem)))) { + ipos++; + } + if (parts.get(ipos) instanceof ExtendsActionItem) { + ExtendsActionItem et = (ExtendsActionItem) parts.get(ipos); + extendsOp = getWithoutGlobal(et.superclass); + ipos++; + } + if (parts.get(ipos) instanceof StoreRegisterActionItem) { + StoreRegisterActionItem sr = (StoreRegisterActionItem) parts.get(ipos); + int instanceReg = sr.register.number; + if (sr.value instanceof GetMemberActionItem) { + GetMemberActionItem gm = (GetMemberActionItem) sr.value; + //gm.memberName should be "prototype" + if (gm.object instanceof TemporaryRegister) { + TemporaryRegister tm = (TemporaryRegister) gm.object; + int classReg = tm.getRegId(); + if (tm.value instanceof SetMemberActionItem) { + SetMemberActionItem sm = (SetMemberActionItem) tm.value; + if (sm.value instanceof StoreRegisterActionItem) { + sr = (StoreRegisterActionItem) sm.value; + if (sr.value instanceof FunctionActionItem) { + ((FunctionActionItem) (sr.value)).calculatedFunctionName = (className instanceof GetMemberActionItem) ? ((GetMemberActionItem) className).memberName : className; + functions.add((FunctionActionItem) sr.value); + + for (; ipos < parts.size(); ipos++) { + if (parts.get(ipos) instanceof ImplementsOpActionItem) { + ImplementsOpActionItem io = (ImplementsOpActionItem) parts.get(ipos); + implementsOp = io.superclasses; + continue; + } + if (parts.get(ipos) instanceof SetMemberActionItem) { + sm = (SetMemberActionItem) parts.get(ipos); + int rnum = -1; + if (sm.object instanceof DirectValueActionItem) { + DirectValueActionItem dv = (DirectValueActionItem) sm.object; + if (dv.value instanceof RegisterNumber) { + RegisterNumber rn = (RegisterNumber) dv.value; + rnum = rn.number; + } + } + if (sm.object instanceof TemporaryRegister) { + rnum = ((TemporaryRegister) sm.object).getRegId(); + } + if (rnum == instanceReg) { + if (sm.value instanceof FunctionActionItem) { + ((FunctionActionItem) sm.value).calculatedFunctionName = sm.objectName; + functions.add((FunctionActionItem) sm.value); + } else { + vars.add(new MyEntry<>(sm.objectName, sm.value)); + } + } else if (rnum == classReg) { + if (sm.value instanceof FunctionActionItem) { + ((FunctionActionItem) sm.value).calculatedFunctionName = sm.objectName; + staticFunctions.add((FunctionActionItem) sm.value); + } else { + staticVars.add(new MyEntry<>(sm.objectName, sm.value)); + } + } + + } + } + + } + + } + } + List output2 = new ArrayList<>(); + for (int i = 0; i < prevCount; i++) { + output2.add(output.get(i)); + } + output2.add(new ClassActionItem(className, extendsOp, implementsOp, null/*FIXME*/, functions, vars, staticFunctions, staticVars)); + return output2; + } + } + } else if (parts.get(ipos) instanceof SetMemberActionItem) { + SetMemberActionItem sm = (SetMemberActionItem) parts.get(0); + if (sm.value instanceof FunctionActionItem) { + FunctionActionItem f = (FunctionActionItem) sm.value; + if (f.actions.isEmpty()) { + if (parts.size() == 2) { + if (parts.get(1) instanceof ImplementsOpActionItem) { + ImplementsOpActionItem iot = (ImplementsOpActionItem) parts.get(1); + implementsOp = iot.superclasses; + } else { + //ok = false; + break; + } + } + List output2 = new ArrayList<>(); + for (int i = 0; i < prevCount; i++) { + output2.add(output.get(i)); + } + output2.add(new InterfaceActionItem(sm.objectName, implementsOp)); + return output2; + } + } + } + } + } + } else { + //ok = false; + } + } else { + ok = false; + } + } else { + ok = false; + } + } else if (!(t instanceof PopItem)) { + prevCount++; + //ok = false; + } + if (!ok) { + break; + } + } + return output; + } + + @Override + public boolean ignoredLoops() { + return false; + } + + public static void setConstantPool(List actions, ConstantPool cpool) { + for (GraphSourceItem a : actions) { + if (a instanceof ActionPush) { + if (cpool != null) { + ((ActionPush) a).constantPool = cpool.constants; + } + } + if (a instanceof ActionDefineFunction) { + if (cpool != null) { + //((ActionDefineFunction) a).setConstantPool(cpool.constants,actions); + } + } + if (a instanceof ActionDefineFunction2) { + if (cpool != null) { + //((ActionDefineFunction2) a).setConstantPool(cpool.constants,actions); + } + } + } + } + + public GraphTextWriter getASMSourceReplaced(ActionList container, Set knownAddreses, ScriptExportMode exportMode, GraphTextWriter writer) { + writer.appendNoHilight(getASMSource(container, knownAddreses, exportMode)); + return writer; + } + + public static double toFloatPoint(Object o) { + if (o instanceof Double) { + return (Double) o; + } + if (o instanceof Integer) { + return (Integer) o; + } + if (o instanceof Long) { + return (Long) o; + } + if (o == Null.INSTANCE) { + return Double.NaN; + } + if (o == Undefined.INSTANCE) { + return Double.NaN; + } + if (o instanceof Boolean) { + return (Boolean) o ? 1.0 : 0.0; + } + if (o instanceof String) { + try { + return Double.parseDouble((String) o); + } catch (NumberFormatException nfe) { + return Double.NaN; + } + } + return 0; + } + + public static GraphTargetItem gettoset(GraphTargetItem get, GraphTargetItem value, List variables) { + GraphTargetItem ret = get; + boolean boxed = false; + if (get instanceof VariableActionItem) { + boxed = true; + ret = ((VariableActionItem) ret).getBoxedValue(); + } + if (ret instanceof GetVariableActionItem) { + GetVariableActionItem gv = (GetVariableActionItem) ret; + ret = new SetVariableActionItem(null, null, gv.name, value); + } else if (ret instanceof GetMemberActionItem) { + GetMemberActionItem mem = (GetMemberActionItem) ret; + ret = new SetMemberActionItem(null, null, mem.object, mem.memberName, value); + } else if ((ret instanceof DirectValueActionItem) && ((DirectValueActionItem) ret).value instanceof RegisterNumber) { + ret = new StoreRegisterActionItem(null, null, (RegisterNumber) ((DirectValueActionItem) ret).value, value, false); + } else if (ret instanceof GetPropertyActionItem) { + GetPropertyActionItem gp = (GetPropertyActionItem) ret; + ret = new SetPropertyActionItem(null, null, gp.target, gp.propertyIndex, value); + } + if (boxed) { + GraphTargetItem b = ret; + ret = new VariableActionItem(((VariableActionItem) get).getVariableName(), value, ((VariableActionItem) get).isDefinition()); + ((VariableActionItem) ret).setBoxedValue((ActionItem) b); + variables.remove((VariableActionItem) get); + variables.add((VariableActionItem) ret); + } + return ret; + } + + @Override + public boolean isDeobfuscatePop() { + return false; + } + + @Override + public int getLine() { + return 0; + } + + @Override + public String getFile() { + return null; + } +} 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 71afa4a19..adf5b2f20 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 @@ -1,1057 +1,1062 @@ -/* - * Copyright (C) 2010-2016 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.configuration; - -import com.jpexs.decompiler.flash.ApplicationInfo; -import com.jpexs.decompiler.flash.exporters.modes.ExeExportMode; -import com.jpexs.decompiler.flash.helpers.CodeFormatting; -import com.jpexs.decompiler.flash.importers.TextImportResizeTextBoundsMode; -import com.jpexs.helpers.Helper; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.swing.JOptionPane; - -/** - * - * @author JPEXS - */ -public class Configuration { - - private static final String CONFIG_NAME = "config.bin"; - - private static final File unspecifiedFile = new File("unspecified"); - - private static File directory = unspecifiedFile; - - public static final Level logLevel; - - public static boolean showStat; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("ui") - public static final ConfigurationItem openMultipleFiles = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("script") - public static final ConfigurationItem decompile = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("decompilation") - public static final ConfigurationItem parallelSpeedUp = null; - - @ConfigurationDefaultInt(10) - @ConfigurationCategory("decompilation") - public static final ConfigurationItem parallelSpeedUpThreadCount = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem autoDeobfuscate = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("") - public static final ConfigurationItem cacheOnDisk = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("") - public static final ConfigurationItem cacheImages = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("display") - public static final ConfigurationItem internalFlashViewer = null; - - @ConfigurationDefaultInt(1000) - @ConfigurationCategory("display") - public static final ConfigurationItem setMovieDelay = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("display") - public static final ConfigurationItem dumpView = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("display") - public static final ConfigurationItem useHexColorFormat = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("display") - public static final ConfigurationItem showOldTextDuringTextEditing = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("ui") - public static final ConfigurationItem gotoMainClassOnStartup = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem autoRenameIdentifiers = null; - - @ConfigurationDefaultBoolean(false) - public static final ConfigurationItem offeredAssociation = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem decimalAddress = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem showAllAddresses = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("display") - public static final ConfigurationItem useFrameCache = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("ui") - public static final ConfigurationItem useRibbonInterface = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("export") - public static final ConfigurationItem overwriteExistingFiles = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("export") - public static final ConfigurationItem openFolderAfterFlaExport = null; - - @ConfigurationCategory("export") - public static final ConfigurationItem overrideTextExportFileName = null; - - @ConfigurationDefaultBoolean(false) - public static final ConfigurationItem useDetailedLogging = null; - - /** - * Debug mode = throwing an error when comparing original file and - * recompiled - */ - @ConfigurationDefaultBoolean(false) - @ConfigurationInternal - public static final ConfigurationItem _debugMode = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationInternal - public static final ConfigurationItem _showDebugMenu = null; - - /** - * Turn off resolving constants in ActionScript 2 - */ - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("script") - public static final ConfigurationItem resolveConstants = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - @ConfigurationInternal - public static final ConfigurationItem showOriginalBytesInPcodeHex = null; - - /** - * Limit of code subs (for obfuscated code) - */ - @ConfigurationDefaultInt(500) - @ConfigurationCategory("limit") - public static final ConfigurationItem sublimiter = null; - - /** - * Total export timeout in seconds - */ - @ConfigurationDefaultInt(30 * 60) - @ConfigurationCategory("limit") - public static final ConfigurationItem exportTimeout = null; - - /** - * Decompilation timeout in seconds for a single file - */ - @ConfigurationDefaultInt(5 * 60) - @ConfigurationCategory("limit") - public static final ConfigurationItem decompilationTimeoutFile = null; - - /** - * Using parameter names in decompiling may cause problems because official - * programs like Flash CS 5.5 inserts wrong parameter names indices - */ - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem paramNamesEnable = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("ui") - public static final ConfigurationItem displayFileName = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationInternal - public static final ConfigurationItem _debugCopy = null; - - @ConfigurationDefaultBoolean(false) - public static final ConfigurationItem dumpTags = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("export") - public static final ConfigurationItem setFFDecVersionInExportedFont = null; - - @ConfigurationDefaultInt(60) - @ConfigurationCategory("limit") - public static final ConfigurationItem decompilationTimeoutSingleMethod = null; - - @ConfigurationDefaultInt(1) - public static final ConfigurationItem lastRenameType = null; - - @ConfigurationDefaultString(".") - @ConfigurationDirectory - public static final ConfigurationItem lastSaveDir = null; - - @ConfigurationDefaultString(".") - @ConfigurationDirectory - public static final ConfigurationItem lastOpenDir = null; - - @ConfigurationDefaultString(".") - @ConfigurationDirectory - public static final ConfigurationItem lastExportDir = null; - - @ConfigurationDefaultString("en") - @ConfigurationCategory("ui") - public static final ConfigurationItem locale = null; - - @ConfigurationDefaultString("_loc%d_") - @ConfigurationCategory("script") - public static final ConfigurationItem registerNameFormat = null; - - @ConfigurationDefaultInt(15) - public static final ConfigurationItem maxRecentFileCount = null; - - public static final ConfigurationItem recentFiles = null; - - public static final ConfigurationItem> fontPairingMap = null; - - public static final ConfigurationItem> swfSpecificConfigs = null; - - @ConfigurationDefaultCalendar(0) - public static final ConfigurationItem lastUpdatesCheckDate = null; - - @ConfigurationDefaultInt(1000) - @ConfigurationName("gui.window.width") - public static final ConfigurationItem guiWindowWidth = null; - - @ConfigurationDefaultInt(700) - @ConfigurationName("gui.window.height") - public static final ConfigurationItem guiWindowHeight = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationName("gui.window.maximized.horizontal") - public static final ConfigurationItem guiWindowMaximizedHorizontal = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationName("gui.window.maximized.vertical") - public static final ConfigurationItem guiWindowMaximizedVertical = null; - - @ConfigurationDefaultDouble(0.5) - @ConfigurationName("gui.avm2.splitPane.dividerLocationPercent") - @ConfigurationInternal - public static final ConfigurationItem guiAvm2SplitPaneDividerLocationPercent = null; - - @ConfigurationDefaultDouble(0.5) - @ConfigurationName("gui.actionSplitPane.dividerLocationPercent") - @ConfigurationInternal - public static final ConfigurationItem guiActionSplitPaneDividerLocationPercent = null; - - @ConfigurationDefaultDouble(0.5) - @ConfigurationName("gui.previewSplitPane.dividerLocationPercent") - @ConfigurationInternal - public static final ConfigurationItem guiPreviewSplitPaneDividerLocationPercent = null; - - @ConfigurationDefaultDouble(0.3333333333) - @ConfigurationName("gui.splitPane1.dividerLocationPercent") - @ConfigurationInternal - public static final ConfigurationItem guiSplitPane1DividerLocationPercent = null; - - @ConfigurationDefaultDouble(0.6) - @ConfigurationName("gui.splitPane2.dividerLocationPercent") - @ConfigurationInternal - public static final ConfigurationItem guiSplitPane2DividerLocationPercent = null; - - @ConfigurationDefaultDouble(0.5) - @ConfigurationName("gui.timeLineSplitPane.dividerLocationPercent") - @ConfigurationInternal - public static final ConfigurationItem guiTimeLineSplitPaneDividerLocationPercent = null; - - @ConfigurationDefaultDouble(0.6) - @ConfigurationName("gui.dump.splitPane.dividerLocationPercent") - @ConfigurationInternal - public static final ConfigurationItem guiDumpSplitPaneDividerLocationPercent = null; - - @ConfigurationDefaultString("com.jpexs.decompiler.flash.gui.OceanicSkin") - @ConfigurationName("gui.skin") - @ConfigurationCategory("ui") - public static final ConfigurationItem guiSkin = null; - - @ConfigurationDefaultInt(3) - @ConfigurationCategory("export") - public static final ConfigurationItem saveAsExeScaleMode = null; - - @ConfigurationCategory("export") - public static final ConfigurationItem exeExportMode = null; - - @ConfigurationDefaultInt(1024 * 1024/*1MiB*/) - @ConfigurationCategory("limit") - public static final ConfigurationItem syntaxHighlightLimit = null; - - public static final ConfigurationItem guiFontPreviewSampleText = null; - - @ConfigurationName("gui.fontPreviewWindow.width") - public static final ConfigurationItem guiFontPreviewWidth = null; - - @ConfigurationName("gui.fontPreviewWindow.height") - public static final ConfigurationItem guiFontPreviewHeight = null; - - @ConfigurationName("gui.fontPreviewWindow.posX") - public static final ConfigurationItem guiFontPreviewPosX = null; - - @ConfigurationName("gui.fontPreviewWindow.posY") - public static final ConfigurationItem guiFontPreviewPosY = null; - - @ConfigurationDefaultInt(3) - @ConfigurationName("formatting.indent.size") - @ConfigurationCategory("format") - public static final ConfigurationItem indentSize = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationName("formatting.indent.useTabs") - @ConfigurationCategory("format") - public static final ConfigurationItem indentUseTabs = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("format") - public static final ConfigurationItem beginBlockOnNewLine = null; - - @ConfigurationDefaultInt(1000 * 60 * 60 * 24) - @ConfigurationCategory("update") - @ConfigurationName("check.updates.delay") - public static final ConfigurationItem checkForUpdatesDelay = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("update") - @ConfigurationName("check.updates.stable") - public static final ConfigurationItem checkForUpdatesStable = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("update") - @ConfigurationName("check.updates.nightly") - public static final ConfigurationItem checkForUpdatesNightly = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("update") - @ConfigurationName("check.updates.enabled") - public static final ConfigurationItem checkForUpdatesAuto = null; - - @ConfigurationCategory("update") - public static final ConfigurationItem updateProxyAddress = null; - - @ConfigurationDefaultString("") - @ConfigurationName("export.formats") - public static final ConfigurationItem lastSelectedExportFormats = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("export") - public static final ConfigurationItem textExportSingleFile = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("export") - public static final ConfigurationItem scriptExportSingleFile = null; - - @ConfigurationDefaultString("--- SEPARATOR ---") - @ConfigurationCategory("export") - public static final ConfigurationItem textExportSingleFileSeparator = null; - - @ConfigurationDefaultString("--- RECORDSEPARATOR ---") - @ConfigurationCategory("export") - public static final ConfigurationItem textExportSingleFileRecordSeparator = null; - - @ConfigurationCategory("import") - public static final ConfigurationItem textImportResizeTextBoundsMode = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("import") - public static final ConfigurationItem resetLetterSpacingOnTextImport = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationName("warning.experimental.as12edit") - @ConfigurationCategory("script") - public static final ConfigurationItem warningExperimentalAS12Edit = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationName("warning.experimental.as3edit") - @ConfigurationCategory("script") - public static final ConfigurationItem warningExperimentalAS3Edit = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("script") - public static final ConfigurationItem showCodeSavedMessage = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("script") - public static final ConfigurationItem showTraitSavedMessage = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("export") - public static final ConfigurationItem packJavaScripts = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("export") - public static final ConfigurationItem textExportExportFontFace = null; - - @ConfigurationDefaultInt(128) - public static final ConfigurationItem lzmaFastBytes = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem showMethodBodyId = null; - - @ConfigurationDefaultDouble(1.0) - @ConfigurationName("export.zoom") - public static final ConfigurationItem lastSelectedExportZoom = null; - - public static final ConfigurationItem pluginPath = null; - - @ConfigurationDefaultInt(55556) - @ConfigurationCategory("script") - public static final ConfigurationItem debuggerPort = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem randomDebuggerPackage = null; - - @ConfigurationDefaultBoolean(true) - public static final ConfigurationItem displayDebuggerInfo = null; - - @ConfigurationDefaultString("debugConsole") - public static final ConfigurationItem lastDebuggerReplaceFunction = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("script") - public static final ConfigurationItem getLocalNamesFromDebugInfo = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("ui") - public static final ConfigurationItem tagTreeShowEmptyFolders = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("ui") - public static final ConfigurationItem autoLoadEmbeddedSwfs = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("ui") - public static final ConfigurationItem showCloseConfirmation = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("ui") - public static final ConfigurationItem editorMode = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("ui") - public static final ConfigurationItem autoSaveTagModifications = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("ui") - public static final ConfigurationItem saveSessionOnExit = null; - - public static final ConfigurationItem lastSessionFiles = null; - - public static final ConfigurationItem lastSessionFileTitles = null; - - public static final ConfigurationItem lastSessionSelection = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("ui") - public static final ConfigurationItem loopMedia = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("ui") - public static final ConfigurationItem allowOnlyOneInstance = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem ignoreCLikePackages = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem smartNumberFormatting = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem enableScriptInitializerDisplay = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("ui") - public static final ConfigurationItem autoOpenLoadedSWFs = null; - - @ConfigurationDefaultString("") - @ConfigurationCategory("paths") - @ConfigurationFile - public static final ConfigurationItem playerLocation = null; - - @ConfigurationDefaultString("") - @ConfigurationCategory("paths") - @ConfigurationFile - public static final ConfigurationItem playerDebugLocation = null; - - @ConfigurationDefaultString("") - @ConfigurationCategory("paths") - @ConfigurationFile(".*\\.swc$") - public static final ConfigurationItem playerLibLocation = null; - - @ConfigurationDefaultDouble(0.7) - @ConfigurationName("gui.avm2.splitPane.vars.dividerLocationPercent") - @ConfigurationInternal - public static final ConfigurationItem guiAvm2VarsSplitPaneDividerLocationPercent = null; - - @ConfigurationDefaultDouble(0.7) - @ConfigurationName("gui.action.splitPane.vars.dividerLocationPercent") - @ConfigurationInternal - public static final ConfigurationItem guiActionVarsSplitPaneDividerLocationPercent = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("script") - public static final ConfigurationItem debugHalt = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationName("warning.svgImport") - @ConfigurationCategory("import") - public static final ConfigurationItem warningSvgImport = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationName("warning.hexViewNotUpToDate") - @ConfigurationCategory("import") - public static final ConfigurationItem warningHexViewNotUpToDate = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationName("shapeImport.useNonSmoothedFill") - @ConfigurationCategory("import") - public static final ConfigurationItem shapeImportUseNonSmoothedFill = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("display") - @ConfigurationName("internalFlashViewer.execute.as12") - public static final ConfigurationItem internalFlashViewerExecuteAs12 = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem displayDupInstructions = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("script") - public static final ConfigurationItem useRegExprLiteral = null; - - @ConfigurationDefaultBoolean(true) - @ConfigurationCategory("script") - public static final ConfigurationItem handleSkinPartsAutomatically = null; - - @ConfigurationDefaultBoolean(false) - //@ConfigurationCategory("script") - @ConfigurationInternal - public static final ConfigurationItem _ignoreAdditionalFlexClasses = null; - - @ConfigurationDefaultBoolean(false) - //@ConfigurationCategory("script") - @ConfigurationInternal - public static final ConfigurationItem _enableFlexExport = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationCategory("script") - public static final ConfigurationItem simplifyExpressions = null; - - @ConfigurationDefaultBoolean(false) - @ConfigurationInternal - public static final ConfigurationItem hwAcceleratedGraphics = null; - - @ConfigurationDefaultDouble(0.85) - @ConfigurationName("gui.avm2.splitPane.docs.dividerLocationPercent") - @ConfigurationInternal - public static final ConfigurationItem guiAvm2DocsSplitPaneDividerLocationPercent = null; - - private enum OSId { - - WINDOWS, OSX, UNIX - } - - private static OSId getOSId() { - PrivilegedAction doGetOSName = new PrivilegedAction() { - @Override - public String run() { - return System.getProperty("os.name"); - } - }; - OSId id = OSId.UNIX; - String osName = AccessController.doPrivileged(doGetOSName); - if (osName != null) { - if (osName.toLowerCase().startsWith("mac os x")) { - id = OSId.OSX; - } else if (osName.contains("Windows")) { - id = OSId.WINDOWS; - } - } - return id; - } - - public static String getFFDecHome() { - if (directory == unspecifiedFile) { - directory = null; - String userHome = null; - try { - userHome = System.getProperty("user.home"); - } catch (SecurityException ignore) { - } - if (userHome != null) { - String applicationId = ApplicationInfo.SHORT_APPLICATION_NAME; - OSId osId = getOSId(); - if (osId == OSId.WINDOWS) { - File appDataDir = null; - try { - String appDataEV = System.getenv("APPDATA"); - if ((appDataEV != null) && (appDataEV.length() > 0)) { - appDataDir = new File(appDataEV); - } - } catch (SecurityException ignore) { - } - String vendorId = ApplicationInfo.VENDOR; - if ((appDataDir != null) && appDataDir.isDirectory()) { - // ${APPDATA}\{vendorId}\${applicationId} - String path = vendorId + "\\" + applicationId + "\\"; - directory = new File(appDataDir, path); - } else { - // ${userHome}\Application Data\${vendorId}\${applicationId} - String path = "Application Data\\" + vendorId + "\\" + applicationId + "\\"; - directory = new File(userHome, path); - } - } else if (osId == OSId.OSX) { - // ${userHome}/Library/Application Support/${applicationId} - String path = "Library/Application Support/" + applicationId + "/"; - directory = new File(userHome, path); - } else { - // ${userHome}/.${applicationId}/ - String path = "." + applicationId + "/"; - directory = new File(userHome, path); - } - } else { - //no home, then use application directory - directory = new File("."); - } - } - if (!directory.exists()) { - if (!directory.mkdirs()) { - if (!directory.exists()) { - directory = new File("."); //fallback to current directory - } - } - } - String ret = directory.getAbsolutePath(); - if (!ret.endsWith(File.separator)) { - ret += File.separator; - } - return ret; - } - - public static List getRecentFiles() { - String files = recentFiles.get(); - if (files == null || files.isEmpty()) { - return new ArrayList<>(); - } - return Arrays.asList(files.split("::")); - } - - public static void addRecentFile(String path) { - List recentFilesArray = new ArrayList<>(getRecentFiles()); - int idx = recentFilesArray.indexOf(path); - if (idx != -1) { - recentFilesArray.remove(idx); - } - recentFilesArray.add(path); - while (recentFilesArray.size() > maxRecentFileCount.get()) { - recentFilesArray.remove(0); - } - recentFiles.set(Helper.joinStrings(recentFilesArray, "::")); - } - - public static void removeRecentFile(String path) { - List recentFilesArray = new ArrayList<>(getRecentFiles()); - int idx = recentFilesArray.indexOf(path); - if (idx != -1) { - recentFilesArray.remove(idx); - } - recentFiles.set(Helper.joinStrings(recentFilesArray, "::")); - } - - public static Map getFontToNameMap() { - HashMap map = fontPairingMap.get(); - if (map == null) { - map = new HashMap<>(); - fontPairingMap.set(map); - } - - return map; - } - - public static void addFontPair(String fileName, int fontId, String fontName, String installedName) { - Map fontPairs = getFontToNameMap(); - fontPairs.put(fontName, installedName); - - SwfSpecificConfiguration swfConf = getOrCreateSwfSpecificConfiguration(fileName); - swfConf.fontPairingMap.put(fontId + "_" + fontName, installedName); - } - - public static SwfSpecificConfiguration getSwfSpecificConfiguration(String fileName) { - HashMap map = swfSpecificConfigs.get(); - if (map == null) { - map = new HashMap<>(); - swfSpecificConfigs.set(map); - } - - return map.get(fileName); - } - - public static SwfSpecificConfiguration getOrCreateSwfSpecificConfiguration(String fileName) { - SwfSpecificConfiguration swfConf = getSwfSpecificConfiguration(fileName); - if (swfConf == null) { - swfConf = new SwfSpecificConfiguration(); - swfSpecificConfigs.get().put(fileName, swfConf); - } - - return swfConf; - } - - private static String getConfigFile() throws IOException { - return getFFDecHome() + CONFIG_NAME; - } - - private static HashMap loadFromFile(String file) { - try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { - - @SuppressWarnings("unchecked") - HashMap cfg = (HashMap) ois.readObject(); - return cfg; - } catch (ClassNotFoundException | IOException ex) { - // ignore - } - - return new HashMap<>(); - } - - private static void saveToFile(String file) { - HashMap config = new HashMap<>(); - for (Entry entry : getConfigurationFields().entrySet()) { - try { - String name = entry.getKey(); - Field field = entry.getValue(); - ConfigurationItem item = (ConfigurationItem) field.get(null); - if (item.hasValue) { - config.put(name, item.get()); - } - } catch (IllegalArgumentException | IllegalAccessException ex) { - Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); - } - } - try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { - oos.writeObject(config); - } catch (IOException ex) { - //TODO: move this to GUI - JOptionPane.showMessageDialog(null, "Cannot save configuration.", "Error", JOptionPane.ERROR_MESSAGE); - Logger.getLogger(Configuration.class.getName()).severe("Configuration directory is read only."); - } - } - - public static void saveConfig() { - try { - saveToFile(getConfigFile()); - } catch (IOException ex) { - // ignore - } - } - - static { - setConfigurationFields(); - if (useDetailedLogging.get()) { - logLevel = Level.FINEST; - } else if (_debugMode.get()) { - logLevel = Level.INFO; - } else { - logLevel = Level.WARNING; - } - //limit paralel threads? - //int processorCount = Runtime.getRuntime().availableProcessors(); - } - - @SuppressWarnings("unchecked") - public static void setConfigurationFields() { - try { - HashMap config = loadFromFile(getConfigFile()); - for (Entry entry : getConfigurationFields().entrySet()) { - String name = entry.getKey(); - Field field = entry.getValue(); - // remove final modifier from field - Field modifiersField = field.getClass().getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - - Object defaultValue = getDefaultValue(field); - Object value = null; - if (config.containsKey(name)) { - value = config.get(name); - - Class type; - Type type2 = ((ParameterizedType) (field.getGenericType())).getActualTypeArguments()[0]; - if (type2 instanceof Class) { - type = (Class) type2; - } else { - type = (Class) ((ParameterizedType) type2).getRawType(); - } - if (value != null && !type.isAssignableFrom(value.getClass())) { - System.out.println("Configuration item has a wrong type: " + name + " expected: " + type.getSimpleName() + " actual: " + value.getClass().getSimpleName()); - value = null; - } - } - - if (value != null) { - field.set(null, new ConfigurationItem(name, defaultValue, value)); - } else { - field.set(null, new ConfigurationItem(name, defaultValue)); - } - } - } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException ex) { - // Reflection exceptions. This should never happen - throw new Error(ex.getMessage()); - } catch (IOException ex) { - Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); - } - if (playerLibLocation.get("").isEmpty()) { - File swcFile = getPlayerSwcOld(); - if (swcFile != null) { - playerLibLocation.set(swcFile.getAbsolutePath()); - } - } - } - - public static Object getDefaultValue(Field field) { - Object defaultValue = null; - ConfigurationDefaultBoolean aBool = field.getAnnotation(ConfigurationDefaultBoolean.class); - if (aBool != null) { - defaultValue = aBool.value(); - } - ConfigurationDefaultInt aInt = field.getAnnotation(ConfigurationDefaultInt.class); - if (aInt != null) { - defaultValue = aInt.value(); - } - ConfigurationDefaultString aString = field.getAnnotation(ConfigurationDefaultString.class); - if (aString != null) { - defaultValue = aString.value(); - } - ConfigurationDefaultDouble aDouble = field.getAnnotation(ConfigurationDefaultDouble.class); - if (aDouble != null) { - defaultValue = aDouble.value(); - } - ConfigurationDefaultCalendar aCalendar = field.getAnnotation(ConfigurationDefaultCalendar.class); - if (aCalendar != null) { - GregorianCalendar mingc = new GregorianCalendar(); - mingc.setTime(new Date(aCalendar.value())); - defaultValue = mingc; - } - return defaultValue; - } - - public static Map getConfigurationFields() { - Field[] fields = Configuration.class.getFields(); - Map result = new HashMap<>(); - for (Field field : fields) { - if (ConfigurationItem.class.isAssignableFrom(field.getType())) { - ConfigurationName annotation = field.getAnnotation(ConfigurationName.class); - String name = annotation == null ? field.getName() : annotation.value(); - result.put(name, field); - } - } - return result; - } - - public static CodeFormatting getCodeFormatting() { - CodeFormatting ret = new CodeFormatting(); - String indentString = ""; - for (int i = 0; i < indentSize.get(); i++) { - indentString += indentUseTabs.get() ? "\t" : " "; - } - ret.indentString = indentString; - ret.beginBlockOnNewLine = beginBlockOnNewLine.get(); - return ret; - } - - public static int getParallelThreadCount() { - int count = parallelSpeedUpThreadCount.get(); - if (count < 2) { - count = 2; - } - - return count; - } - - public static File getPath(String folder) { - String home = getFFDecHome(); - File dir = new File(home + folder); - if (!dir.exists()) { - dir.mkdirs(); - } - return dir; - } - - public static File getFlashLibPath() { - return getPath("flashlib"); - } - - public static File getProjectorPath() { - return getPath("projector"); - } - - private static String getDownloadsHtml() throws IOException { - String html = Helper.downloadUrlString("https://www.adobe.com/support/flashplayer/downloads.html"); - return html; - } - - private static String getUrlFromDownloadsHtml(String urlPatternString) { - try { - String html = getDownloadsHtml(); - Pattern urlPattern = Pattern.compile(urlPatternString, Pattern.DOTALL); - Matcher matcher = urlPattern.matcher(html); - if (matcher.matches()) { - String url = matcher.group(1); - int a = url.length(); - return url; - } - - return null; - } catch (IOException ex) { - Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); - return null; - } - } - - private static String getLatestPlayerGlobalUrl() { - return getUrlFromDownloadsHtml(".* libNames = new ArrayList<>(); - for (File f : libs) { - libNames.add(f.getName()); - } - Collections.sort(libNames); - if (!libNames.isEmpty()) { - return new File(libsDir.getAbsolutePath() + File.separator + libNames.get(libNames.size() - 1)); - } else { - return null; - } - } - - return null; - } - - public static File getProjectorFile(ExeExportMode exportMode) { - File projectoDir = getProjectorPath(); - if (projectoDir != null && projectoDir.exists()) { - File[] projectors = projectoDir.listFiles(new FilenameFilter() { - - @Override - public boolean accept(File dir, String name) { - switch (exportMode) { - case PROJECTOR_WIN: - return name.toLowerCase().endsWith(".exe"); - case PROJECTOR_MAC: - return name.toLowerCase().endsWith(".dmg"); - case PROJECTOR_LINUX: - return name.toLowerCase().endsWith(".gz"); - } - - return false; - } - }); - List projectorNames = new ArrayList<>(); - for (File f : projectors) { - projectorNames.add(f.getName()); - } - Collections.sort(projectorNames); - if (!projectorNames.isEmpty()) { - return new File(projectoDir.getAbsolutePath() + File.separator + projectorNames.get(projectorNames.size() - 1)); - } else { - return null; - } - } - - return null; - } -} +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.configuration; + +import com.jpexs.decompiler.flash.ApplicationInfo; +import com.jpexs.decompiler.flash.exporters.modes.ExeExportMode; +import com.jpexs.decompiler.flash.helpers.CodeFormatting; +import com.jpexs.decompiler.flash.importers.TextImportResizeTextBoundsMode; +import com.jpexs.helpers.Helper; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.JOptionPane; + +/** + * + * @author JPEXS + */ +public class Configuration { + + private static final String CONFIG_NAME = "config.bin"; + + private static final File unspecifiedFile = new File("unspecified"); + + private static File directory = unspecifiedFile; + + public static final Level logLevel; + + public static boolean showStat; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("ui") + public static final ConfigurationItem openMultipleFiles = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("script") + public static final ConfigurationItem decompile = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("decompilation") + public static final ConfigurationItem parallelSpeedUp = null; + + @ConfigurationDefaultInt(10) + @ConfigurationCategory("decompilation") + public static final ConfigurationItem parallelSpeedUpThreadCount = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem autoDeobfuscate = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("") + public static final ConfigurationItem cacheOnDisk = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("") + public static final ConfigurationItem cacheImages = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("display") + public static final ConfigurationItem internalFlashViewer = null; + + @ConfigurationDefaultInt(1000) + @ConfigurationCategory("display") + public static final ConfigurationItem setMovieDelay = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("display") + public static final ConfigurationItem dumpView = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("display") + public static final ConfigurationItem useHexColorFormat = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("display") + public static final ConfigurationItem showOldTextDuringTextEditing = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("ui") + public static final ConfigurationItem gotoMainClassOnStartup = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem autoRenameIdentifiers = null; + + @ConfigurationDefaultBoolean(false) + public static final ConfigurationItem offeredAssociation = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem decimalAddress = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem showAllAddresses = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("display") + public static final ConfigurationItem useFrameCache = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("ui") + public static final ConfigurationItem useRibbonInterface = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("export") + public static final ConfigurationItem overwriteExistingFiles = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("export") + public static final ConfigurationItem openFolderAfterFlaExport = null; + + @ConfigurationCategory("export") + public static final ConfigurationItem overrideTextExportFileName = null; + + @ConfigurationDefaultBoolean(false) + public static final ConfigurationItem useDetailedLogging = null; + + /** + * Debug mode = throwing an error when comparing original file and + * recompiled + */ + @ConfigurationDefaultBoolean(false) + @ConfigurationInternal + public static final ConfigurationItem _debugMode = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationInternal + public static final ConfigurationItem _showDebugMenu = null; + + /** + * Turn off resolving constants in ActionScript 2 + */ + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("script") + public static final ConfigurationItem resolveConstants = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + @ConfigurationInternal + public static final ConfigurationItem showFileOffsetInPcodeHex = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + @ConfigurationInternal + public static final ConfigurationItem showOriginalBytesInPcodeHex = null; + + /** + * Limit of code subs (for obfuscated code) + */ + @ConfigurationDefaultInt(500) + @ConfigurationCategory("limit") + public static final ConfigurationItem sublimiter = null; + + /** + * Total export timeout in seconds + */ + @ConfigurationDefaultInt(30 * 60) + @ConfigurationCategory("limit") + public static final ConfigurationItem exportTimeout = null; + + /** + * Decompilation timeout in seconds for a single file + */ + @ConfigurationDefaultInt(5 * 60) + @ConfigurationCategory("limit") + public static final ConfigurationItem decompilationTimeoutFile = null; + + /** + * Using parameter names in decompiling may cause problems because official + * programs like Flash CS 5.5 inserts wrong parameter names indices + */ + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem paramNamesEnable = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("ui") + public static final ConfigurationItem displayFileName = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationInternal + public static final ConfigurationItem _debugCopy = null; + + @ConfigurationDefaultBoolean(false) + public static final ConfigurationItem dumpTags = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("export") + public static final ConfigurationItem setFFDecVersionInExportedFont = null; + + @ConfigurationDefaultInt(60) + @ConfigurationCategory("limit") + public static final ConfigurationItem decompilationTimeoutSingleMethod = null; + + @ConfigurationDefaultInt(1) + public static final ConfigurationItem lastRenameType = null; + + @ConfigurationDefaultString(".") + @ConfigurationDirectory + public static final ConfigurationItem lastSaveDir = null; + + @ConfigurationDefaultString(".") + @ConfigurationDirectory + public static final ConfigurationItem lastOpenDir = null; + + @ConfigurationDefaultString(".") + @ConfigurationDirectory + public static final ConfigurationItem lastExportDir = null; + + @ConfigurationDefaultString("en") + @ConfigurationCategory("ui") + public static final ConfigurationItem locale = null; + + @ConfigurationDefaultString("_loc%d_") + @ConfigurationCategory("script") + public static final ConfigurationItem registerNameFormat = null; + + @ConfigurationDefaultInt(15) + public static final ConfigurationItem maxRecentFileCount = null; + + public static final ConfigurationItem recentFiles = null; + + public static final ConfigurationItem> fontPairingMap = null; + + public static final ConfigurationItem> swfSpecificConfigs = null; + + @ConfigurationDefaultCalendar(0) + public static final ConfigurationItem lastUpdatesCheckDate = null; + + @ConfigurationDefaultInt(1000) + @ConfigurationName("gui.window.width") + public static final ConfigurationItem guiWindowWidth = null; + + @ConfigurationDefaultInt(700) + @ConfigurationName("gui.window.height") + public static final ConfigurationItem guiWindowHeight = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationName("gui.window.maximized.horizontal") + public static final ConfigurationItem guiWindowMaximizedHorizontal = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationName("gui.window.maximized.vertical") + public static final ConfigurationItem guiWindowMaximizedVertical = null; + + @ConfigurationDefaultDouble(0.5) + @ConfigurationName("gui.avm2.splitPane.dividerLocationPercent") + @ConfigurationInternal + public static final ConfigurationItem guiAvm2SplitPaneDividerLocationPercent = null; + + @ConfigurationDefaultDouble(0.5) + @ConfigurationName("gui.actionSplitPane.dividerLocationPercent") + @ConfigurationInternal + public static final ConfigurationItem guiActionSplitPaneDividerLocationPercent = null; + + @ConfigurationDefaultDouble(0.5) + @ConfigurationName("gui.previewSplitPane.dividerLocationPercent") + @ConfigurationInternal + public static final ConfigurationItem guiPreviewSplitPaneDividerLocationPercent = null; + + @ConfigurationDefaultDouble(0.3333333333) + @ConfigurationName("gui.splitPane1.dividerLocationPercent") + @ConfigurationInternal + public static final ConfigurationItem guiSplitPane1DividerLocationPercent = null; + + @ConfigurationDefaultDouble(0.6) + @ConfigurationName("gui.splitPane2.dividerLocationPercent") + @ConfigurationInternal + public static final ConfigurationItem guiSplitPane2DividerLocationPercent = null; + + @ConfigurationDefaultDouble(0.5) + @ConfigurationName("gui.timeLineSplitPane.dividerLocationPercent") + @ConfigurationInternal + public static final ConfigurationItem guiTimeLineSplitPaneDividerLocationPercent = null; + + @ConfigurationDefaultDouble(0.6) + @ConfigurationName("gui.dump.splitPane.dividerLocationPercent") + @ConfigurationInternal + public static final ConfigurationItem guiDumpSplitPaneDividerLocationPercent = null; + + @ConfigurationDefaultString("com.jpexs.decompiler.flash.gui.OceanicSkin") + @ConfigurationName("gui.skin") + @ConfigurationCategory("ui") + public static final ConfigurationItem guiSkin = null; + + @ConfigurationDefaultInt(3) + @ConfigurationCategory("export") + public static final ConfigurationItem saveAsExeScaleMode = null; + + @ConfigurationCategory("export") + public static final ConfigurationItem exeExportMode = null; + + @ConfigurationDefaultInt(1024 * 1024/*1MiB*/) + @ConfigurationCategory("limit") + public static final ConfigurationItem syntaxHighlightLimit = null; + + public static final ConfigurationItem guiFontPreviewSampleText = null; + + @ConfigurationName("gui.fontPreviewWindow.width") + public static final ConfigurationItem guiFontPreviewWidth = null; + + @ConfigurationName("gui.fontPreviewWindow.height") + public static final ConfigurationItem guiFontPreviewHeight = null; + + @ConfigurationName("gui.fontPreviewWindow.posX") + public static final ConfigurationItem guiFontPreviewPosX = null; + + @ConfigurationName("gui.fontPreviewWindow.posY") + public static final ConfigurationItem guiFontPreviewPosY = null; + + @ConfigurationDefaultInt(3) + @ConfigurationName("formatting.indent.size") + @ConfigurationCategory("format") + public static final ConfigurationItem indentSize = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationName("formatting.indent.useTabs") + @ConfigurationCategory("format") + public static final ConfigurationItem indentUseTabs = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("format") + public static final ConfigurationItem beginBlockOnNewLine = null; + + @ConfigurationDefaultInt(1000 * 60 * 60 * 24) + @ConfigurationCategory("update") + @ConfigurationName("check.updates.delay") + public static final ConfigurationItem checkForUpdatesDelay = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("update") + @ConfigurationName("check.updates.stable") + public static final ConfigurationItem checkForUpdatesStable = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("update") + @ConfigurationName("check.updates.nightly") + public static final ConfigurationItem checkForUpdatesNightly = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("update") + @ConfigurationName("check.updates.enabled") + public static final ConfigurationItem checkForUpdatesAuto = null; + + @ConfigurationCategory("update") + public static final ConfigurationItem updateProxyAddress = null; + + @ConfigurationDefaultString("") + @ConfigurationName("export.formats") + public static final ConfigurationItem lastSelectedExportFormats = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("export") + public static final ConfigurationItem textExportSingleFile = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("export") + public static final ConfigurationItem scriptExportSingleFile = null; + + @ConfigurationDefaultString("--- SEPARATOR ---") + @ConfigurationCategory("export") + public static final ConfigurationItem textExportSingleFileSeparator = null; + + @ConfigurationDefaultString("--- RECORDSEPARATOR ---") + @ConfigurationCategory("export") + public static final ConfigurationItem textExportSingleFileRecordSeparator = null; + + @ConfigurationCategory("import") + public static final ConfigurationItem textImportResizeTextBoundsMode = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("import") + public static final ConfigurationItem resetLetterSpacingOnTextImport = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationName("warning.experimental.as12edit") + @ConfigurationCategory("script") + public static final ConfigurationItem warningExperimentalAS12Edit = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationName("warning.experimental.as3edit") + @ConfigurationCategory("script") + public static final ConfigurationItem warningExperimentalAS3Edit = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("script") + public static final ConfigurationItem showCodeSavedMessage = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("script") + public static final ConfigurationItem showTraitSavedMessage = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("export") + public static final ConfigurationItem packJavaScripts = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("export") + public static final ConfigurationItem textExportExportFontFace = null; + + @ConfigurationDefaultInt(128) + public static final ConfigurationItem lzmaFastBytes = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem showMethodBodyId = null; + + @ConfigurationDefaultDouble(1.0) + @ConfigurationName("export.zoom") + public static final ConfigurationItem lastSelectedExportZoom = null; + + public static final ConfigurationItem pluginPath = null; + + @ConfigurationDefaultInt(55556) + @ConfigurationCategory("script") + public static final ConfigurationItem debuggerPort = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem randomDebuggerPackage = null; + + @ConfigurationDefaultBoolean(true) + public static final ConfigurationItem displayDebuggerInfo = null; + + @ConfigurationDefaultString("debugConsole") + public static final ConfigurationItem lastDebuggerReplaceFunction = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("script") + public static final ConfigurationItem getLocalNamesFromDebugInfo = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("ui") + public static final ConfigurationItem tagTreeShowEmptyFolders = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("ui") + public static final ConfigurationItem autoLoadEmbeddedSwfs = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("ui") + public static final ConfigurationItem showCloseConfirmation = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("ui") + public static final ConfigurationItem editorMode = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("ui") + public static final ConfigurationItem autoSaveTagModifications = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("ui") + public static final ConfigurationItem saveSessionOnExit = null; + + public static final ConfigurationItem lastSessionFiles = null; + + public static final ConfigurationItem lastSessionFileTitles = null; + + public static final ConfigurationItem lastSessionSelection = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("ui") + public static final ConfigurationItem loopMedia = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("ui") + public static final ConfigurationItem allowOnlyOneInstance = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem ignoreCLikePackages = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem smartNumberFormatting = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem enableScriptInitializerDisplay = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("ui") + public static final ConfigurationItem autoOpenLoadedSWFs = null; + + @ConfigurationDefaultString("") + @ConfigurationCategory("paths") + @ConfigurationFile + public static final ConfigurationItem playerLocation = null; + + @ConfigurationDefaultString("") + @ConfigurationCategory("paths") + @ConfigurationFile + public static final ConfigurationItem playerDebugLocation = null; + + @ConfigurationDefaultString("") + @ConfigurationCategory("paths") + @ConfigurationFile(".*\\.swc$") + public static final ConfigurationItem playerLibLocation = null; + + @ConfigurationDefaultDouble(0.7) + @ConfigurationName("gui.avm2.splitPane.vars.dividerLocationPercent") + @ConfigurationInternal + public static final ConfigurationItem guiAvm2VarsSplitPaneDividerLocationPercent = null; + + @ConfigurationDefaultDouble(0.7) + @ConfigurationName("gui.action.splitPane.vars.dividerLocationPercent") + @ConfigurationInternal + public static final ConfigurationItem guiActionVarsSplitPaneDividerLocationPercent = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("script") + public static final ConfigurationItem debugHalt = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationName("warning.svgImport") + @ConfigurationCategory("import") + public static final ConfigurationItem warningSvgImport = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationName("warning.hexViewNotUpToDate") + @ConfigurationCategory("import") + public static final ConfigurationItem warningHexViewNotUpToDate = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationName("shapeImport.useNonSmoothedFill") + @ConfigurationCategory("import") + public static final ConfigurationItem shapeImportUseNonSmoothedFill = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("display") + @ConfigurationName("internalFlashViewer.execute.as12") + public static final ConfigurationItem internalFlashViewerExecuteAs12 = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem displayDupInstructions = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("script") + public static final ConfigurationItem useRegExprLiteral = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("script") + public static final ConfigurationItem handleSkinPartsAutomatically = null; + + @ConfigurationDefaultBoolean(false) + //@ConfigurationCategory("script") + @ConfigurationInternal + public static final ConfigurationItem _ignoreAdditionalFlexClasses = null; + + @ConfigurationDefaultBoolean(false) + //@ConfigurationCategory("script") + @ConfigurationInternal + public static final ConfigurationItem _enableFlexExport = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static final ConfigurationItem simplifyExpressions = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationInternal + public static final ConfigurationItem hwAcceleratedGraphics = null; + + @ConfigurationDefaultDouble(0.85) + @ConfigurationName("gui.avm2.splitPane.docs.dividerLocationPercent") + @ConfigurationInternal + public static final ConfigurationItem guiAvm2DocsSplitPaneDividerLocationPercent = null; + + private enum OSId { + + WINDOWS, OSX, UNIX + } + + private static OSId getOSId() { + PrivilegedAction doGetOSName = new PrivilegedAction() { + @Override + public String run() { + return System.getProperty("os.name"); + } + }; + OSId id = OSId.UNIX; + String osName = AccessController.doPrivileged(doGetOSName); + if (osName != null) { + if (osName.toLowerCase().startsWith("mac os x")) { + id = OSId.OSX; + } else if (osName.contains("Windows")) { + id = OSId.WINDOWS; + } + } + return id; + } + + public static String getFFDecHome() { + if (directory == unspecifiedFile) { + directory = null; + String userHome = null; + try { + userHome = System.getProperty("user.home"); + } catch (SecurityException ignore) { + } + if (userHome != null) { + String applicationId = ApplicationInfo.SHORT_APPLICATION_NAME; + OSId osId = getOSId(); + if (osId == OSId.WINDOWS) { + File appDataDir = null; + try { + String appDataEV = System.getenv("APPDATA"); + if ((appDataEV != null) && (appDataEV.length() > 0)) { + appDataDir = new File(appDataEV); + } + } catch (SecurityException ignore) { + } + String vendorId = ApplicationInfo.VENDOR; + if ((appDataDir != null) && appDataDir.isDirectory()) { + // ${APPDATA}\{vendorId}\${applicationId} + String path = vendorId + "\\" + applicationId + "\\"; + directory = new File(appDataDir, path); + } else { + // ${userHome}\Application Data\${vendorId}\${applicationId} + String path = "Application Data\\" + vendorId + "\\" + applicationId + "\\"; + directory = new File(userHome, path); + } + } else if (osId == OSId.OSX) { + // ${userHome}/Library/Application Support/${applicationId} + String path = "Library/Application Support/" + applicationId + "/"; + directory = new File(userHome, path); + } else { + // ${userHome}/.${applicationId}/ + String path = "." + applicationId + "/"; + directory = new File(userHome, path); + } + } else { + //no home, then use application directory + directory = new File("."); + } + } + if (!directory.exists()) { + if (!directory.mkdirs()) { + if (!directory.exists()) { + directory = new File("."); //fallback to current directory + } + } + } + String ret = directory.getAbsolutePath(); + if (!ret.endsWith(File.separator)) { + ret += File.separator; + } + return ret; + } + + public static List getRecentFiles() { + String files = recentFiles.get(); + if (files == null || files.isEmpty()) { + return new ArrayList<>(); + } + return Arrays.asList(files.split("::")); + } + + public static void addRecentFile(String path) { + List recentFilesArray = new ArrayList<>(getRecentFiles()); + int idx = recentFilesArray.indexOf(path); + if (idx != -1) { + recentFilesArray.remove(idx); + } + recentFilesArray.add(path); + while (recentFilesArray.size() > maxRecentFileCount.get()) { + recentFilesArray.remove(0); + } + recentFiles.set(Helper.joinStrings(recentFilesArray, "::")); + } + + public static void removeRecentFile(String path) { + List recentFilesArray = new ArrayList<>(getRecentFiles()); + int idx = recentFilesArray.indexOf(path); + if (idx != -1) { + recentFilesArray.remove(idx); + } + recentFiles.set(Helper.joinStrings(recentFilesArray, "::")); + } + + public static Map getFontToNameMap() { + HashMap map = fontPairingMap.get(); + if (map == null) { + map = new HashMap<>(); + fontPairingMap.set(map); + } + + return map; + } + + public static void addFontPair(String fileName, int fontId, String fontName, String installedName) { + Map fontPairs = getFontToNameMap(); + fontPairs.put(fontName, installedName); + + SwfSpecificConfiguration swfConf = getOrCreateSwfSpecificConfiguration(fileName); + swfConf.fontPairingMap.put(fontId + "_" + fontName, installedName); + } + + public static SwfSpecificConfiguration getSwfSpecificConfiguration(String fileName) { + HashMap map = swfSpecificConfigs.get(); + if (map == null) { + map = new HashMap<>(); + swfSpecificConfigs.set(map); + } + + return map.get(fileName); + } + + public static SwfSpecificConfiguration getOrCreateSwfSpecificConfiguration(String fileName) { + SwfSpecificConfiguration swfConf = getSwfSpecificConfiguration(fileName); + if (swfConf == null) { + swfConf = new SwfSpecificConfiguration(); + swfSpecificConfigs.get().put(fileName, swfConf); + } + + return swfConf; + } + + private static String getConfigFile() throws IOException { + return getFFDecHome() + CONFIG_NAME; + } + + private static HashMap loadFromFile(String file) { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { + + @SuppressWarnings("unchecked") + HashMap cfg = (HashMap) ois.readObject(); + return cfg; + } catch (ClassNotFoundException | IOException ex) { + // ignore + } + + return new HashMap<>(); + } + + private static void saveToFile(String file) { + HashMap config = new HashMap<>(); + for (Entry entry : getConfigurationFields().entrySet()) { + try { + String name = entry.getKey(); + Field field = entry.getValue(); + ConfigurationItem item = (ConfigurationItem) field.get(null); + if (item.hasValue) { + config.put(name, item.get()); + } + } catch (IllegalArgumentException | IllegalAccessException ex) { + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); + } + } + try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { + oos.writeObject(config); + } catch (IOException ex) { + //TODO: move this to GUI + JOptionPane.showMessageDialog(null, "Cannot save configuration.", "Error", JOptionPane.ERROR_MESSAGE); + Logger.getLogger(Configuration.class.getName()).severe("Configuration directory is read only."); + } + } + + public static void saveConfig() { + try { + saveToFile(getConfigFile()); + } catch (IOException ex) { + // ignore + } + } + + static { + setConfigurationFields(); + if (useDetailedLogging.get()) { + logLevel = Level.FINEST; + } else if (_debugMode.get()) { + logLevel = Level.INFO; + } else { + logLevel = Level.WARNING; + } + //limit paralel threads? + //int processorCount = Runtime.getRuntime().availableProcessors(); + } + + @SuppressWarnings("unchecked") + public static void setConfigurationFields() { + try { + HashMap config = loadFromFile(getConfigFile()); + for (Entry entry : getConfigurationFields().entrySet()) { + String name = entry.getKey(); + Field field = entry.getValue(); + // remove final modifier from field + Field modifiersField = field.getClass().getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + + Object defaultValue = getDefaultValue(field); + Object value = null; + if (config.containsKey(name)) { + value = config.get(name); + + Class type; + Type type2 = ((ParameterizedType) (field.getGenericType())).getActualTypeArguments()[0]; + if (type2 instanceof Class) { + type = (Class) type2; + } else { + type = (Class) ((ParameterizedType) type2).getRawType(); + } + if (value != null && !type.isAssignableFrom(value.getClass())) { + System.out.println("Configuration item has a wrong type: " + name + " expected: " + type.getSimpleName() + " actual: " + value.getClass().getSimpleName()); + value = null; + } + } + + if (value != null) { + field.set(null, new ConfigurationItem(name, defaultValue, value)); + } else { + field.set(null, new ConfigurationItem(name, defaultValue)); + } + } + } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException ex) { + // Reflection exceptions. This should never happen + throw new Error(ex.getMessage()); + } catch (IOException ex) { + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); + } + if (playerLibLocation.get("").isEmpty()) { + File swcFile = getPlayerSwcOld(); + if (swcFile != null) { + playerLibLocation.set(swcFile.getAbsolutePath()); + } + } + } + + public static Object getDefaultValue(Field field) { + Object defaultValue = null; + ConfigurationDefaultBoolean aBool = field.getAnnotation(ConfigurationDefaultBoolean.class); + if (aBool != null) { + defaultValue = aBool.value(); + } + ConfigurationDefaultInt aInt = field.getAnnotation(ConfigurationDefaultInt.class); + if (aInt != null) { + defaultValue = aInt.value(); + } + ConfigurationDefaultString aString = field.getAnnotation(ConfigurationDefaultString.class); + if (aString != null) { + defaultValue = aString.value(); + } + ConfigurationDefaultDouble aDouble = field.getAnnotation(ConfigurationDefaultDouble.class); + if (aDouble != null) { + defaultValue = aDouble.value(); + } + ConfigurationDefaultCalendar aCalendar = field.getAnnotation(ConfigurationDefaultCalendar.class); + if (aCalendar != null) { + GregorianCalendar mingc = new GregorianCalendar(); + mingc.setTime(new Date(aCalendar.value())); + defaultValue = mingc; + } + return defaultValue; + } + + public static Map getConfigurationFields() { + Field[] fields = Configuration.class.getFields(); + Map result = new HashMap<>(); + for (Field field : fields) { + if (ConfigurationItem.class.isAssignableFrom(field.getType())) { + ConfigurationName annotation = field.getAnnotation(ConfigurationName.class); + String name = annotation == null ? field.getName() : annotation.value(); + result.put(name, field); + } + } + return result; + } + + public static CodeFormatting getCodeFormatting() { + CodeFormatting ret = new CodeFormatting(); + String indentString = ""; + for (int i = 0; i < indentSize.get(); i++) { + indentString += indentUseTabs.get() ? "\t" : " "; + } + ret.indentString = indentString; + ret.beginBlockOnNewLine = beginBlockOnNewLine.get(); + return ret; + } + + public static int getParallelThreadCount() { + int count = parallelSpeedUpThreadCount.get(); + if (count < 2) { + count = 2; + } + + return count; + } + + public static File getPath(String folder) { + String home = getFFDecHome(); + File dir = new File(home + folder); + if (!dir.exists()) { + dir.mkdirs(); + } + return dir; + } + + public static File getFlashLibPath() { + return getPath("flashlib"); + } + + public static File getProjectorPath() { + return getPath("projector"); + } + + private static String getDownloadsHtml() throws IOException { + String html = Helper.downloadUrlString("https://www.adobe.com/support/flashplayer/downloads.html"); + return html; + } + + private static String getUrlFromDownloadsHtml(String urlPatternString) { + try { + String html = getDownloadsHtml(); + Pattern urlPattern = Pattern.compile(urlPatternString, Pattern.DOTALL); + Matcher matcher = urlPattern.matcher(html); + if (matcher.matches()) { + String url = matcher.group(1); + int a = url.length(); + return url; + } + + return null; + } catch (IOException ex) { + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); + return null; + } + } + + private static String getLatestPlayerGlobalUrl() { + return getUrlFromDownloadsHtml(".* libNames = new ArrayList<>(); + for (File f : libs) { + libNames.add(f.getName()); + } + Collections.sort(libNames); + if (!libNames.isEmpty()) { + return new File(libsDir.getAbsolutePath() + File.separator + libNames.get(libNames.size() - 1)); + } else { + return null; + } + } + + return null; + } + + public static File getProjectorFile(ExeExportMode exportMode) { + File projectoDir = getProjectorPath(); + if (projectoDir != null && projectoDir.exists()) { + File[] projectors = projectoDir.listFiles(new FilenameFilter() { + + @Override + public boolean accept(File dir, String name) { + switch (exportMode) { + case PROJECTOR_WIN: + return name.toLowerCase().endsWith(".exe"); + case PROJECTOR_MAC: + return name.toLowerCase().endsWith(".dmg"); + case PROJECTOR_LINUX: + return name.toLowerCase().endsWith(".gz"); + } + + return false; + } + }); + List projectorNames = new ArrayList<>(); + for (File f : projectors) { + projectorNames.add(f.getName()); + } + Collections.sort(projectorNames); + if (!projectorNames.isEmpty()) { + return new File(projectoDir.getAbsolutePath() + File.separator + projectorNames.get(projectorNames.size() - 1)); + } else { + return null; + } + } + + return null; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java index 96dbc5ab2..cc92435ef 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java @@ -63,7 +63,7 @@ public class ShapeExporter { return ret; } - File foutdir = new File(outdir); + File foutdir = new File(outdir); Path.createDirectorySafe(foutdir); int count = 0; diff --git a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java index e3a30d09f..7a0e5b9d7 100644 --- a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java @@ -1,1048 +1,1065 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.action; - -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.ActionList; -import com.jpexs.decompiler.flash.action.CachedScript; -import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; -import com.jpexs.decompiler.flash.action.parser.ActionParseException; -import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser; -import com.jpexs.decompiler.flash.action.parser.script.ActionScript2Parser; -import com.jpexs.decompiler.flash.action.parser.script.ActionScriptLexer; -import com.jpexs.decompiler.flash.action.parser.script.ParsedSymbol; -import com.jpexs.decompiler.flash.action.parser.script.SymbolType; -import com.jpexs.decompiler.flash.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.gui.AppStrings; -import com.jpexs.decompiler.flash.gui.DebugPanel; -import com.jpexs.decompiler.flash.gui.DebuggerHandler; -import com.jpexs.decompiler.flash.gui.GraphDialog; -import com.jpexs.decompiler.flash.gui.HeaderLabel; -import com.jpexs.decompiler.flash.gui.Main; -import com.jpexs.decompiler.flash.gui.MainPanel; -import com.jpexs.decompiler.flash.gui.SearchListener; -import com.jpexs.decompiler.flash.gui.SearchPanel; -import com.jpexs.decompiler.flash.gui.TagEditorPanel; -import com.jpexs.decompiler.flash.gui.View; -import com.jpexs.decompiler.flash.gui.controls.JPersistentSplitPane; -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.HighlightedText; -import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; -import com.jpexs.decompiler.flash.helpers.hilight.HighlightData; -import com.jpexs.decompiler.flash.helpers.hilight.Highlighting; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.graph.CompilationException; -import com.jpexs.helpers.CancellableWorker; -import com.jpexs.helpers.Helper; -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Font; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.CancellationException; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Pattern; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JToggleButton; -import javax.swing.SwingConstants; -import javax.swing.event.CaretEvent; -import javax.swing.event.CaretListener; -import javax.swing.text.Highlighter; -import javax.swing.tree.TreePath; -import jsyntaxpane.SyntaxDocument; -import jsyntaxpane.Token; -import jsyntaxpane.TokenType; -import jsyntaxpane.actions.ActionUtils; - -/** - * - * @author JPEXS - */ -public class ActionPanel extends JPanel implements SearchListener, TagEditorPanel { - - private static final Logger logger = Logger.getLogger(ActionPanel.class.getName()); - - private MainPanel mainPanel; - - public DebuggableEditorPane editor; - - public DebuggableEditorPane decompiledEditor; - - public JPersistentSplitPane splitPane; - - public JButton saveButton = new JButton(AppStrings.translate("button.save"), View.getIcon("save16")); - - public JButton editButton = new JButton(AppStrings.translate("button.edit"), View.getIcon("edit16")); - - public JButton cancelButton = new JButton(AppStrings.translate("button.cancel"), View.getIcon("cancel16")); - - public JLabel experimentalLabel = new JLabel(AppStrings.translate("action.edit.experimental")); - - public JButton editDecompiledButton = new JButton(AppStrings.translate("button.edit"), View.getIcon("edit16")); - - public JButton saveDecompiledButton = new JButton(AppStrings.translate("button.save"), View.getIcon("save16")); - - public JButton cancelDecompiledButton = new JButton(AppStrings.translate("button.cancel"), View.getIcon("cancel16")); - - public JToggleButton hexButton; - - public JToggleButton hexOnlyButton; - - public JToggleButton constantsViewButton; - - public JToggleButton resolveConstantsButton; - - public JToggleButton showOriginalBytesInPcodeHexButton; - - public JLabel asmLabel = new HeaderLabel(AppStrings.translate("panel.disassembled")); - - public JLabel decLabel = new HeaderLabel(AppStrings.translate("panel.decompiled")); - - public List decompiledHilights = new ArrayList<>(); - - public List specialHighlights = new ArrayList<>(); - - public List classHighlights = new ArrayList<>(); - - public List methodHighlights = new ArrayList<>(); - - public List disassembledHilights = new ArrayList<>(); - - private boolean ignoreCarret = false; - - private boolean editMode = false; - - private boolean editDecompiledMode = false; - - private ActionList lastCode; - - private ASMSource src; - - public JPanel topButtonsPan; - - private HighlightedText srcWithHex; - - private HighlightedText srcNoHex; - - private HighlightedText srcHexOnly; - - private HighlightedText srcConstants; - - private String lastDecompiled = ""; - - private ASMSource lastASM; - - public SearchPanel searchPanel; - - private CancellableWorker setSourceWorker; - - public void clearSource() { - lastCode = null; - lastASM = null; - lastDecompiled = null; - searchPanel.clear(); - src = null; - srcWithHex = null; - srcNoHex = null; - srcHexOnly = null; - srcConstants = null; - } - - public String getStringUnderCursor() { - int pos = decompiledEditor.getCaretPosition(); - - SyntaxDocument sDoc = ActionUtils.getSyntaxDocument(decompiledEditor); - if (sDoc != null) { - Token t = sDoc.getTokenAt(pos + 1); - String ident = null; - //It should be identifier or obfuscated identifier - if (t != null && (t.type == TokenType.IDENTIFIER || t.type == TokenType.REGEX)) { - CharSequence tData = t.getText(sDoc); - ident = tData.toString(); - //We need to get unescaped identifier, so we use our Lexer - ActionScriptLexer lex = new ActionScriptLexer(new StringReader(ident)); - try { - ParsedSymbol symb = lex.lex(); - if (symb.type == SymbolType.IDENTIFIER) { - ident = (String) symb.value; - } else { - ident = null; - } - } catch (IOException | ActionParseException ex) { - ident = null; - } - } - if (ident == null) { - Highlighting h = Highlighting.searchPos(decompiledHilights, pos); - if (h != null) { - List list = lastCode; - Action lastIns = null; - int inspos = 0; - Action selIns = null; - for (Action ins : list) { - if (h.getProperties().offset == ins.getAddress()) { - selIns = ins; - break; - } - if (ins.getAddress() > h.getProperties().offset && lastIns != null) { - inspos = (int) (h.getProperties().offset - lastIns.getAddress()); - selIns = lastIns; - break; - } - lastIns = ins; - } - if (selIns != null) { - if (selIns instanceof ActionPush) { - ActionPush ap = (ActionPush) selIns; - Object var = ap.values.get(inspos - 1); - String identifier = null; - if (var instanceof String) { - identifier = (String) var; - } - if (var instanceof ConstantIndex) { - identifier = ap.constantPool.get(((ConstantIndex) var).index); - } - return identifier; - } - } - - } - } else { - return ident; - } - } - return null; - } - - public List search(SWF swf, final String txt, boolean ignoreCase, boolean regexp, CancellableWorker worker) { - if (txt != null && !txt.isEmpty()) { - searchPanel.setOptions(ignoreCase, regexp); - Map asms = swf.getASMs(false); - final List found = new ArrayList<>(); - Pattern pat; - if (regexp) { - pat = Pattern.compile(txt, ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0); - } else { - pat = Pattern.compile(Pattern.quote(txt), ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0); - } - int pos = 0; - for (Entry item : asms.entrySet()) { - pos++; - String workText = AppStrings.translate("work.searching"); - String decAdd = ""; - ASMSource asm = item.getValue(); - if (!SWF.isCached(asm)) { - decAdd = ", " + AppStrings.translate("work.decompiling"); - } - - Main.startWork(workText + " \"" + txt + "\"" + decAdd + " - (" + pos + "/" + asms.size() + ") " + item.getKey() + "... ", worker); - try { - if (pat.matcher(SWF.getCached(asm, null).text).find()) { - found.add(new ActionSearchResult(asm, item.getKey())); - } - } catch (InterruptedException ex) { - break; - } - } - - return found; - } - - return null; - } - - private void setDecompiledText(final String scriptName, final String text) { - View.execInEventDispatch(() -> { - ignoreCarret = true; - decompiledEditor.setScriptName(scriptName); - decompiledEditor.setText(text); - ignoreCarret = false; - }); - } - - private void setEditorText(final String scriptName, final String text, final String contentType) { - View.execInEventDispatch(() -> { - ignoreCarret = true; - editor.setScriptName("#PCODE " + scriptName); - editor.changeContentType(contentType); - editor.setText(text); - ignoreCarret = false; - }); - } - - private void setText(final HighlightedText text, final String contentType, final String scriptName) { - View.execInEventDispatch(() -> { - int pos = editor.getCaretPosition(); - Highlighting lastH = null; - for (Highlighting h : disassembledHilights) { - if (pos < h.startPos) { - break; - } - lastH = h; - } - Long offset = lastH == null ? 0 : lastH.getProperties().offset; - disassembledHilights = text.instructionHilights; - String stripped = text.text; - setEditorText(scriptName, stripped, contentType); - Highlighting h = Highlighting.searchOffset(disassembledHilights, offset); - if (h != null) { - if (h.startPos <= editor.getDocument().getLength()) { - editor.setCaretPosition(h.startPos); - } - } - }); - } - - private HighlightedText getHighlightedText(ScriptExportMode exportMode) { - ASMSource asm = (ASMSource) src; - DisassemblyListener listener = getDisassemblyListener(); - asm.addDisassemblyListener(listener); - HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); - try { - asm.getASMSource(exportMode, writer, lastCode); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - asm.removeDisassemblyListener(listener); - return new HighlightedText(writer); - } - - public void setHex(ScriptExportMode exportMode, String scriptName) { - switch (exportMode) { - case PCODE: - if (srcNoHex == null) { - srcNoHex = getHighlightedText(exportMode); - } - - setText(srcNoHex, "text/flasm", scriptName); - break; - case PCODE_HEX: - if (srcWithHex == null) { - srcWithHex = getHighlightedText(exportMode); - } - - setText(srcWithHex, "text/flasm", scriptName); - break; - case HEX: - if (srcHexOnly == null) { - HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); - Helper.byteArrayToHexWithHeader(writer, src.getActionBytes().getRangeData()); - srcHexOnly = new HighlightedText(writer); - } - - setText(srcHexOnly, "text/plain", scriptName); - break; - case CONSTANTS: - if (srcConstants == null) { - srcConstants = getHighlightedText(exportMode); - } - - setText(srcConstants, "text/plain", scriptName); - break; - default: - throw new Error("Export mode not supported: " + exportMode); - } - } - - private DisassemblyListener getDisassemblyListener() { - DisassemblyListener listener = new DisassemblyListener() { - int percent = 0; - - String phase = ""; - - private void progress(String phase, long pos, long total) { - if (total < 1) { - return; - } - int newpercent = (int) (pos * 100 / total); - if (((newpercent > percent) || (!this.phase.equals(phase))) && newpercent <= 100) { - percent = newpercent; - this.phase = phase; - // todo: honfika: it is very slow to show every percent - setEditorText("-", "; " + AppStrings.translate("work.disassembling") + " - " + phase + " " + percent + "%...", "text/flasm"); - } - } - - @Override - public void progressReading(long pos, long total) { - progress(AppStrings.translate("disassemblingProgress.reading"), pos, total); - } - - @Override - public void progressToString(long pos, long total) { - progress(AppStrings.translate("disassemblingProgress.toString"), pos, total); - } - - @Override - public void progressDeobfuscating(long pos, long total) { - progress(AppStrings.translate("disassemblingProgress.deobfuscating"), pos, total); - } - }; - return listener; - } - - public void setSource(final ASMSource src, final boolean useCache) { - if (setSourceWorker != null) { - setSourceWorker.cancel(true); - setSourceWorker = null; - } - - clearSource(); - this.src = src; - final ASMSource asm = (ASMSource) src; - - if (!useCache) { - SWF.uncache(asm); - } - - boolean decompile = Configuration.decompile.get(); - if (!decompile) { - lastDecompiled = Helper.getDecompilationSkippedComment(); - setDecompiledText(asm.getScriptName(), lastDecompiled); - } else { - CachedScript sc = SWF.getFromCache(asm); - if (sc != null) { - decompile = false; - decompiledHilights = sc.hilights; - methodHighlights = sc.methodHilights; - classHighlights = sc.classHilights; - specialHighlights = sc.specialHilights; - lastDecompiled = sc.text; - lastASM = asm; - setDecompiledText(lastASM.getScriptName(), lastDecompiled); - } - } - - if (!decompile) { - setDecompiledEditMode(false); - } - - final boolean decompileNeeded = decompile; - - CancellableWorker worker = new CancellableWorker() { - - @Override - protected Void doInBackground() throws Exception { - - setEditorText(asm.getScriptName(), "; " + AppStrings.translate("work.disassembling") + "...", "text/flasm"); - if (decompileNeeded) { - setDecompiledText("-", "// " + AppStrings.translate("work.waitingfordissasembly") + "..."); - } - DisassemblyListener listener = getDisassemblyListener(); - asm.addDisassemblyListener(listener); - ActionList actions = asm.getActions(); - lastCode = actions; - asm.removeDisassemblyListener(listener); - setHex(getExportMode(), asm.getScriptName()); - if (decompileNeeded) { - setDecompiledText("-", "// " + AppStrings.translate("work.decompiling") + "..."); - - CachedScript sc = SWF.getCached(asm, actions); - decompiledHilights = sc.hilights; - methodHighlights = sc.methodHilights; - classHighlights = sc.classHilights; - specialHighlights = sc.specialHilights; - lastDecompiled = sc.text; - lastASM = asm; - setDecompiledText(lastASM.getScriptName(), lastDecompiled); - setDecompiledEditMode(false); - } - setEditMode(false); - - return null; - } - - @Override - protected void done() { - View.execInEventDispatch(() -> { - setSourceWorker = null; - if (!Main.isDebugging()) { - Main.stopWork(); - } - - try { - get(); - } catch (CancellationException ex) { - setEditorText("-", "; " + AppStrings.translate("work.canceled"), "text/flasm"); - } catch (Exception ex) { - setDecompiledText("-", "// " + AppStrings.translate("decompilationError") + ": " + ex); - } - }); - } - }; - worker.execute(); - setSourceWorker = worker; - if (!Main.isDebugging()) { - Main.startWork(AppStrings.translate("work.decompiling") + "...", worker); - } - } - - public void hilightOffset(long offset) { - } - - public int getLocalDeclarationOfPos(int pos) { - Highlighting sh = Highlighting.searchPos(specialHighlights, pos); - Highlighting h = Highlighting.searchPos(decompiledHilights, pos); - - if (h == null) { - return -1; - } - - List tms = Highlighting.searchAllPos(methodHighlights, pos); - if (tms.isEmpty()) { - return -1; - } - for (Highlighting tm : tms) { - - List tm_tms = Highlighting.searchAllLocalNames(methodHighlights, tm.getProperties().localName); - //is it already declaration? - if (h.getProperties().declaration || (sh != null && sh.getProperties().declaration)) { - return -1; //no jump - } - - String lname = h.getProperties().localName; - if ("this".equals(lname)) { - Highlighting ch = Highlighting.searchPos(classHighlights, pos); - // int cindex = (int) ch.getProperties().index; - return ch.startPos; - } - - HighlightData hData = h.getProperties(); - HighlightData search = new HighlightData(); - search.declaration = hData.declaration; - //search.declaredType = hData.declaredType; - search.localName = hData.localName; - //search.specialValue = hData.specialValue; - if (search.isEmpty()) { - return -1; - } - search.declaration = true; - - for (Highlighting tm1 : tm_tms) { - Highlighting rh = Highlighting.search(decompiledHilights, search, tm1.startPos, tm1.startPos + tm1.len); - if (rh != null) { - return rh.startPos; - } - } - } - - return -1; - } - - public ActionPanel(MainPanel mainPanel) { - this.mainPanel = mainPanel; - editor = new DebuggableEditorPane(); - editor.setEditable(false); - decompiledEditor = new DebuggableEditorPane(); - decompiledEditor.setEditable(false); - decompiledEditor.setLinkHandler(new LinkHandler() { - - @Override - public boolean isLink(Token token) { - int pos = token.start; - Highlighting h = Highlighting.searchPos(decompiledHilights, pos); - if (h != null) { - if (h.getProperties().localName != null && !h.getProperties().declaration) { - return getLocalDeclarationOfPos(pos) != -1; - } - } - return false; - } - - @Override - public void handleLink(Token token) { - int pos = token.start; - int tpos = getLocalDeclarationOfPos(pos); - if (tpos > -1) { - //System.err.println("goto " + tpos); - decompiledEditor.setCaretPosition(tpos); - } else { - //System.err.println("cannot handle"); - } - } - - @Override - public Highlighter.HighlightPainter linkPainter() { - return decompiledEditor.linkPainter(); - } - }); - - searchPanel = new SearchPanel<>(new FlowLayout(), this); - - JButton graphButton = new JButton(View.getIcon("graph16")); - graphButton.addActionListener(this::graphButtonActionPerformed); - graphButton.setToolTipText(AppStrings.translate("button.viewgraph")); - graphButton.setMargin(new Insets(3, 3, 3, 3)); - - hexButton = new JToggleButton(View.getIcon("hexas16")); - hexButton.addActionListener(this::hexButtonActionPerformed); - hexButton.setToolTipText(AppStrings.translate("button.viewhexpcode")); - hexButton.setMargin(new Insets(3, 3, 3, 3)); - - hexOnlyButton = new JToggleButton(View.getIcon("hex16")); - hexOnlyButton.addActionListener(this::hexOnlyButtonActionPerformed); - hexOnlyButton.setToolTipText(AppStrings.translate("button.viewhex")); - hexOnlyButton.setMargin(new Insets(3, 3, 3, 3)); - - constantsViewButton = new JToggleButton(View.getIcon("constantpool16")); - constantsViewButton.addActionListener(this::constantsViewButtonActionPerformed); - constantsViewButton.setToolTipText(AppStrings.translate("button.viewConstants")); - constantsViewButton.setMargin(new Insets(3, 3, 3, 3)); - - NoneSelectedButtonGroup exportModeButtonGroup = new NoneSelectedButtonGroup(); - exportModeButtonGroup.add(hexButton); - exportModeButtonGroup.add(hexOnlyButton); - exportModeButtonGroup.add(constantsViewButton); - - resolveConstantsButton = new JToggleButton(View.getIcon("resolveconst16")); - resolveConstantsButton.addActionListener(this::resolveConstantsButtonActionPerformed); - resolveConstantsButton.setToolTipText(AppStrings.translate("button.resolveConstants")); - resolveConstantsButton.setMargin(new Insets(3, 3, 3, 3)); - resolveConstantsButton.setSelected(Configuration.resolveConstants.get()); - - showOriginalBytesInPcodeHexButton = new JToggleButton(View.getIcon("originalbytes16")); - showOriginalBytesInPcodeHexButton.addActionListener(this::showOriginalBytesInPcodeHexButtonActionPerformed); - showOriginalBytesInPcodeHexButton.setToolTipText(AppStrings.translate("button.showOriginalBytesInPcodeHex")); - showOriginalBytesInPcodeHexButton.setMargin(new Insets(3, 3, 3, 3)); - showOriginalBytesInPcodeHexButton.setSelected(Configuration.showOriginalBytesInPcodeHex.get()); - - topButtonsPan = new JPanel(); - topButtonsPan.setLayout(new BoxLayout(topButtonsPan, BoxLayout.X_AXIS)); - topButtonsPan.add(graphButton); - topButtonsPan.add(Box.createRigidArea(new Dimension(10, 0))); - topButtonsPan.add(hexButton); - topButtonsPan.add(hexOnlyButton); - topButtonsPan.add(constantsViewButton); - topButtonsPan.add(Box.createRigidArea(new Dimension(10, 0))); - topButtonsPan.add(resolveConstantsButton); - topButtonsPan.add(showOriginalBytesInPcodeHexButton); - - JPanel panCode = new JPanel(new BorderLayout()); - panCode.add(new JScrollPane(editor), BorderLayout.CENTER); - panCode.add(topButtonsPan, BorderLayout.NORTH); - - JPanel panB = new JPanel(); - panB.setLayout(new BorderLayout()); - asmLabel.setHorizontalAlignment(SwingConstants.CENTER); - //asmLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); - panB.add(asmLabel, BorderLayout.NORTH); - panB.add(panCode, BorderLayout.CENTER); - - JPanel buttonsPan = new JPanel(); - buttonsPan.setLayout(new FlowLayout()); - buttonsPan.add(editButton); - buttonsPan.add(saveButton); - buttonsPan.add(cancelButton); - - editButton.setMargin(new Insets(3, 3, 3, 10)); - saveButton.setMargin(new Insets(3, 3, 3, 10)); - cancelButton.setMargin(new Insets(3, 3, 3, 10)); - - JPanel decButtonsPan = new JPanel(new FlowLayout()); - decButtonsPan.add(editDecompiledButton); - decButtonsPan.add(experimentalLabel); - decButtonsPan.add(saveDecompiledButton); - decButtonsPan.add(cancelDecompiledButton); - - editDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); - saveDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); - cancelDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); - - //buttonsPan.add(saveHexButton); - //buttonsPan.add(loadHexButton); - panB.add(buttonsPan, BorderLayout.SOUTH); - - saveButton.addActionListener(this::saveActionButtonActionPerformed); - editButton.addActionListener(this::editActionButtonActionPerformed); - cancelButton.addActionListener(this::cancelActionButtonActionPerformed); - saveButton.setVisible(false); - cancelButton.setVisible(false); - - saveDecompiledButton.addActionListener(this::saveDecompiledButtonActionPerformed); - editDecompiledButton.addActionListener(this::editDecompiledButtonActionPerformed); - cancelDecompiledButton.addActionListener(this::cancelDecompiledButtonActionPerformed); - saveDecompiledButton.setVisible(false); - cancelDecompiledButton.setVisible(false); - - JPanel panA = new JPanel(new BorderLayout()); - - panA.add(decLabel, BorderLayout.NORTH); - - DebugPanel debugPanel = new DebugPanel(); - - panA.add(new JPersistentSplitPane(JSplitPane.VERTICAL_SPLIT, new JScrollPane(decompiledEditor), debugPanel, Configuration.guiActionVarsSplitPaneDividerLocationPercent), BorderLayout.CENTER); - panA.add(decButtonsPan, BorderLayout.SOUTH); - - //decPanel.add(searchPanel, BorderLayout.NORTH); - Main.getDebugHandler().addConnectionListener(new DebuggerHandler.ConnectionListener() { - - @Override - public void connected() { - decButtonsPan.setVisible(false); - } - - @Override - public void disconnected() { - decButtonsPan.setVisible(true); - } - }); - - //new JSplitPane(JSplitPane.VERTICAL_SPLIT, decompiledEditor, debugPanel) - //decPanel.add(decButtonsPan, BorderLayout.SOUTH); - //JPanel panBot = new JPanel(new BorderLayout()); - //panBot.add(decButtonsPan, BorderLayout.NORTH); - //panBot.add(debugPanel, BorderLayout.CENTER); - //panA.add(decButtonsPan, BorderLayout.SOUTH); - debugPanel.setVisible(false); - - decLabel.setHorizontalAlignment(SwingConstants.CENTER); - //decLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); - - setLayout(new BorderLayout()); - add(splitPane = new JPersistentSplitPane(JSplitPane.HORIZONTAL_SPLIT, panA, panB, Configuration.guiActionSplitPaneDividerLocationPercent), BorderLayout.CENTER); - - editor.setFont(new Font("Monospaced", Font.PLAIN, editor.getFont().getSize())); - decompiledEditor.setFont(new Font("Monospaced", Font.PLAIN, decompiledEditor.getFont().getSize())); - decompiledEditor.changeContentType("text/actionscript"); - - editor.addCaretListener(new CaretListener() { - @Override - public void caretUpdate(CaretEvent e) { - if (ignoreCarret) { - return; - } - if (editMode || editDecompiledMode) { - return; - } - editor.getCaret().setVisible(true); - int pos = editor.getCaretPosition(); - Highlighting lastH = null; - for (Highlighting h : disassembledHilights) { - if (pos < h.startPos) { - break; - } - lastH = h; - } - Long ofs = lastH == null ? 0 : lastH.getProperties().offset; - Highlighting h2 = Highlighting.searchOffset(decompiledHilights, ofs); - if (h2 != null) { - ignoreCarret = true; - if (h2.startPos <= decompiledEditor.getDocument().getLength()) { - decompiledEditor.setCaretPosition(h2.startPos); - } - decompiledEditor.getCaret().setVisible(true); - ignoreCarret = false; - - } - } - }); - - decompiledEditor.addCaretListener(new CaretListener() { - @Override - public void caretUpdate(CaretEvent e) { - if (ignoreCarret) { - return; - } - if (editMode || editDecompiledMode) { - return; - } - decompiledEditor.getCaret().setVisible(true); - int pos = decompiledEditor.getCaretPosition(); - Highlighting h = Highlighting.searchPos(decompiledHilights, pos); - if (h != null) { - Highlighting h2 = Highlighting.searchOffset(disassembledHilights, h.getProperties().offset); - if (h2 != null) { - ignoreCarret = true; - if (h2.startPos > 0 && h2.startPos < editor.getText().length()) { - editor.setCaretPosition(h2.startPos); - } - editor.getCaret().setVisible(true); - ignoreCarret = false; - } - } - } - }); - - editor.addTextChangedListener(this::editorTextChanged); - decompiledEditor.addTextChangedListener(this::decompiledEditorTextChanged); - } - - private void editorTextChanged() { - setModified(true); - } - - private void decompiledEditorTextChanged() { - setDecompiledModified(true); - } - - private boolean isModified() { - return saveButton.isVisible() && saveButton.isEnabled(); - } - - private void setModified(boolean value) { - saveButton.setEnabled(value); - cancelButton.setEnabled(value); - } - - private boolean isDecompiledModified() { - return saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled(); - } - - private void setDecompiledModified(boolean value) { - saveDecompiledButton.setEnabled(value); - cancelDecompiledButton.setEnabled(value); - } - - public void setEditMode(boolean val) { - View.execInEventDispatch(() -> { - if (val) { - if (hexOnlyButton.isSelected()) { - setHex(ScriptExportMode.HEX, src.getScriptName()); - } else if (constantsViewButton.isSelected()) { - setHex(ScriptExportMode.CONSTANTS, src.getScriptName()); - } else { - setHex(ScriptExportMode.PCODE, src.getScriptName()); - } - } - - editor.setEditable(val); - saveButton.setVisible(val); - saveButton.setEnabled(false); - editButton.setVisible(!val); - cancelButton.setVisible(val); - - editor.getCaret().setVisible(true); - asmLabel.setIcon(val ? View.getIcon("editing16") : null); // this line is not working - topButtonsPan.setVisible(!val); - editMode = val; - editor.requestFocusInWindow(); - }); - } - - public void setDecompiledEditMode(boolean val) { - if (lastASM == null) { - return; - } - View.execInEventDispatch(() -> { - int lastLine = decompiledEditor.getLine(); - int prefLines = lastASM.getPrefixLineCount(); - if (val) { - String newText = lastASM.removePrefixAndSuffix(lastDecompiled); - setDecompiledText(lastASM.getScriptName(), newText); - if (lastLine > -1) { - if (lastLine - prefLines >= 0) { - decompiledEditor.gotoLine(lastLine - prefLines + 1); - } - } - } else { - String newText = lastDecompiled; - setDecompiledText(lastASM.getScriptName(), newText); - if (lastLine > -1) { - decompiledEditor.gotoLine(lastLine + prefLines + 1); - } - } - - decompiledEditor.setEditable(val); - saveDecompiledButton.setVisible(val); - saveDecompiledButton.setEnabled(false); - editDecompiledButton.setVisible(!val); - experimentalLabel.setVisible(!val); - cancelDecompiledButton.setVisible(val); - - decompiledEditor.getCaret().setVisible(true); - decLabel.setIcon(val ? View.getIcon("editing16") : null); - editDecompiledMode = val; - decompiledEditor.requestFocusInWindow(); - }); - } - - private void graphButtonActionPerformed(ActionEvent evt) { - if (lastCode != null) { - try { - GraphDialog gf = new GraphDialog(mainPanel.getMainFrame().getWindow(), new ActionGraph(lastCode, new HashMap<>(), new HashMap<>(), new HashMap<>(), SWF.DEFAULT_VERSION), ""); - gf.setVisible(true); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - } - - private void editActionButtonActionPerformed(ActionEvent evt) { - setEditMode(true); - } - - private void hexButtonActionPerformed(ActionEvent evt) { - setHex(getExportMode(), src.getScriptName()); - } - - private void hexOnlyButtonActionPerformed(ActionEvent evt) { - setHex(getExportMode(), src.getScriptName()); - } - - private void constantsViewButtonActionPerformed(ActionEvent evt) { - setHex(getExportMode(), src.getScriptName()); - } - - private void resolveConstantsButtonActionPerformed(ActionEvent evt) { - boolean resolve = resolveConstantsButton.isSelected(); - Configuration.resolveConstants.set(resolve); - - srcWithHex = null; - srcNoHex = null; - // srcHexOnly = null; is not needed since it does not contains the resolved constant names - setHex(getExportMode(), src.getScriptName()); - } - - private void showOriginalBytesInPcodeHexButtonActionPerformed(ActionEvent evt) { - boolean resolve = showOriginalBytesInPcodeHexButton.isSelected(); - Configuration.showOriginalBytesInPcodeHex.set(resolve); - - srcWithHex = null; - setHex(getExportMode(), src.getScriptName()); - } - - private void cancelActionButtonActionPerformed(ActionEvent evt) { - setEditMode(false); - setHex(getExportMode(), src.getScriptName()); - } - - private void saveActionButtonActionPerformed(ActionEvent evt) { - try { - String text = editor.getText(); - String trimmed = text.trim(); - if (trimmed.startsWith(Helper.hexData)) { - src.setActionBytes(Helper.getBytesFromHexaText(text)); - } else if (trimmed.startsWith(Helper.constants)) { - List> constantPools = Helper.getConstantPoolsFromText(text); - try { - Action.setConstantPools(src, constantPools, true); - } catch (ConstantPoolTooBigException ex) { - View.showMessageDialog(this, AppStrings.translate("error.constantPoolTooBig").replace("%index%", Integer.toString(ex.index)).replace("%size%", Integer.toString(ex.size)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } - } else { - src.setActions(ASMParser.parse(0, true, text, src.getSwf().version, false)); - } - - SWF.uncache(src); - src.setModified(); - setSource(this.src, false); - View.showMessageDialog(this, AppStrings.translate("message.action.saved"), AppStrings.translate("dialog.message.title"), JOptionPane.INFORMATION_MESSAGE, Configuration.showCodeSavedMessage); - saveButton.setVisible(false); - cancelButton.setVisible(false); - editButton.setVisible(true); - editor.setEditable(false); - editMode = false; - mainPanel.refreshTree(src.getSwf()); - } catch (IOException ex) { - } catch (ActionParseException ex) { - editor.gotoLine((int) ex.line); - editor.markError(); - View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } catch (Throwable ex) { - logger.log(Level.SEVERE, null, ex); - } - } - - private void editDecompiledButtonActionPerformed(ActionEvent evt) { - if (View.showConfirmDialog(null, AppStrings.translate("message.confirm.experimental.function"), AppStrings.translate("message.warning"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, Configuration.warningExperimentalAS12Edit, JOptionPane.OK_OPTION) == JOptionPane.OK_OPTION) { - setDecompiledEditMode(true); - } - } - - private void cancelDecompiledButtonActionPerformed(ActionEvent evt) { - setDecompiledEditMode(false); - } - - private void saveDecompiledButtonActionPerformed(ActionEvent evt) { - try { - ActionScript2Parser par = new ActionScript2Parser(mainPanel.getCurrentSwf().version); - src.setActions(par.actionsFromString(decompiledEditor.getText())); - SWF.uncache(src); - src.setModified(); - setSource(src, false); - - View.showMessageDialog(this, AppStrings.translate("message.action.saved"), AppStrings.translate("dialog.message.title"), JOptionPane.INFORMATION_MESSAGE, Configuration.showCodeSavedMessage); - setDecompiledEditMode(false); - } catch (IOException ex) { - logger.log(Level.SEVERE, "IOException during action compiling", ex); - } catch (ActionParseException ex) { - View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } catch (CompilationException ex) { - View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } catch (Throwable ex) { - logger.log(Level.SEVERE, null, ex); - } - } - - private ScriptExportMode getExportMode() { - ScriptExportMode exportMode = hexOnlyButton.isSelected() ? ScriptExportMode.HEX - : hexButton.isSelected() ? ScriptExportMode.PCODE_HEX - : constantsViewButton.isSelected() ? ScriptExportMode.CONSTANTS - : ScriptExportMode.PCODE; - return exportMode; - } - - @Override - public void updateSearchPos(ActionSearchResult item) { - TagTreeModel ttm = (TagTreeModel) mainPanel.tagTree.getModel(); - TreePath tp = ttm.getTreePath(item.getSrc()); - mainPanel.tagTree.setSelectionPath(tp); - mainPanel.tagTree.scrollPathToVisible(tp); - decompiledEditor.setCaretPosition(0); - - View.execInEventDispatchLater(() -> { - searchPanel.showQuickFindDialog(decompiledEditor); - }); - } - - @Override - public boolean tryAutoSave() { - // todo: implement - return false; - } - - @Override - public boolean isEditing() { - return (saveButton.isVisible() && saveButton.isEnabled()) - || (saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled()); - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.action; + +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.ActionList; +import com.jpexs.decompiler.flash.action.CachedScript; +import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; +import com.jpexs.decompiler.flash.action.parser.ActionParseException; +import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser; +import com.jpexs.decompiler.flash.action.parser.script.ActionScript2Parser; +import com.jpexs.decompiler.flash.action.parser.script.ActionScriptLexer; +import com.jpexs.decompiler.flash.action.parser.script.ParsedSymbol; +import com.jpexs.decompiler.flash.action.parser.script.SymbolType; +import com.jpexs.decompiler.flash.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.gui.AppStrings; +import com.jpexs.decompiler.flash.gui.DebugPanel; +import com.jpexs.decompiler.flash.gui.DebuggerHandler; +import com.jpexs.decompiler.flash.gui.GraphDialog; +import com.jpexs.decompiler.flash.gui.HeaderLabel; +import com.jpexs.decompiler.flash.gui.Main; +import com.jpexs.decompiler.flash.gui.MainPanel; +import com.jpexs.decompiler.flash.gui.SearchListener; +import com.jpexs.decompiler.flash.gui.SearchPanel; +import com.jpexs.decompiler.flash.gui.TagEditorPanel; +import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.gui.controls.JPersistentSplitPane; +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.HighlightedText; +import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; +import com.jpexs.decompiler.flash.helpers.hilight.HighlightData; +import com.jpexs.decompiler.flash.helpers.hilight.Highlighting; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.graph.CompilationException; +import com.jpexs.helpers.CancellableWorker; +import com.jpexs.helpers.Helper; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CancellationException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JToggleButton; +import javax.swing.SwingConstants; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.text.Highlighter; +import javax.swing.tree.TreePath; +import jsyntaxpane.SyntaxDocument; +import jsyntaxpane.Token; +import jsyntaxpane.TokenType; +import jsyntaxpane.actions.ActionUtils; + +/** + * + * @author JPEXS + */ +public class ActionPanel extends JPanel implements SearchListener, TagEditorPanel { + + private static final Logger logger = Logger.getLogger(ActionPanel.class.getName()); + + private MainPanel mainPanel; + + public DebuggableEditorPane editor; + + public DebuggableEditorPane decompiledEditor; + + public JPersistentSplitPane splitPane; + + public JButton saveButton = new JButton(AppStrings.translate("button.save"), View.getIcon("save16")); + + public JButton editButton = new JButton(AppStrings.translate("button.edit"), View.getIcon("edit16")); + + public JButton cancelButton = new JButton(AppStrings.translate("button.cancel"), View.getIcon("cancel16")); + + public JLabel experimentalLabel = new JLabel(AppStrings.translate("action.edit.experimental")); + + public JButton editDecompiledButton = new JButton(AppStrings.translate("button.edit"), View.getIcon("edit16")); + + public JButton saveDecompiledButton = new JButton(AppStrings.translate("button.save"), View.getIcon("save16")); + + public JButton cancelDecompiledButton = new JButton(AppStrings.translate("button.cancel"), View.getIcon("cancel16")); + + public JToggleButton hexButton; + + public JToggleButton hexOnlyButton; + + public JToggleButton constantsViewButton; + + public JToggleButton resolveConstantsButton; + + public JToggleButton showFileOffsetInPcodeHexButton; + + public JToggleButton showOriginalBytesInPcodeHexButton; + + public JLabel asmLabel = new HeaderLabel(AppStrings.translate("panel.disassembled")); + + public JLabel decLabel = new HeaderLabel(AppStrings.translate("panel.decompiled")); + + public List decompiledHilights = new ArrayList<>(); + + public List specialHighlights = new ArrayList<>(); + + public List classHighlights = new ArrayList<>(); + + public List methodHighlights = new ArrayList<>(); + + public List disassembledHilights = new ArrayList<>(); + + private boolean ignoreCarret = false; + + private boolean editMode = false; + + private boolean editDecompiledMode = false; + + private ActionList lastCode; + + private ASMSource src; + + public JPanel topButtonsPan; + + private HighlightedText srcWithHex; + + private HighlightedText srcNoHex; + + private HighlightedText srcHexOnly; + + private HighlightedText srcConstants; + + private String lastDecompiled = ""; + + private ASMSource lastASM; + + public SearchPanel searchPanel; + + private CancellableWorker setSourceWorker; + + public void clearSource() { + lastCode = null; + lastASM = null; + lastDecompiled = null; + searchPanel.clear(); + src = null; + srcWithHex = null; + srcNoHex = null; + srcHexOnly = null; + srcConstants = null; + } + + public String getStringUnderCursor() { + int pos = decompiledEditor.getCaretPosition(); + + SyntaxDocument sDoc = ActionUtils.getSyntaxDocument(decompiledEditor); + if (sDoc != null) { + Token t = sDoc.getTokenAt(pos + 1); + String ident = null; + //It should be identifier or obfuscated identifier + if (t != null && (t.type == TokenType.IDENTIFIER || t.type == TokenType.REGEX)) { + CharSequence tData = t.getText(sDoc); + ident = tData.toString(); + //We need to get unescaped identifier, so we use our Lexer + ActionScriptLexer lex = new ActionScriptLexer(new StringReader(ident)); + try { + ParsedSymbol symb = lex.lex(); + if (symb.type == SymbolType.IDENTIFIER) { + ident = (String) symb.value; + } else { + ident = null; + } + } catch (IOException | ActionParseException ex) { + ident = null; + } + } + if (ident == null) { + Highlighting h = Highlighting.searchPos(decompiledHilights, pos); + if (h != null) { + List list = lastCode; + Action lastIns = null; + int inspos = 0; + Action selIns = null; + for (Action ins : list) { + if (h.getProperties().offset == ins.getAddress()) { + selIns = ins; + break; + } + if (ins.getAddress() > h.getProperties().offset && lastIns != null) { + inspos = (int) (h.getProperties().offset - lastIns.getAddress()); + selIns = lastIns; + break; + } + lastIns = ins; + } + if (selIns != null) { + if (selIns instanceof ActionPush) { + ActionPush ap = (ActionPush) selIns; + Object var = ap.values.get(inspos - 1); + String identifier = null; + if (var instanceof String) { + identifier = (String) var; + } + if (var instanceof ConstantIndex) { + identifier = ap.constantPool.get(((ConstantIndex) var).index); + } + return identifier; + } + } + + } + } else { + return ident; + } + } + return null; + } + + public List search(SWF swf, final String txt, boolean ignoreCase, boolean regexp, CancellableWorker worker) { + if (txt != null && !txt.isEmpty()) { + searchPanel.setOptions(ignoreCase, regexp); + Map asms = swf.getASMs(false); + final List found = new ArrayList<>(); + Pattern pat; + if (regexp) { + pat = Pattern.compile(txt, ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0); + } else { + pat = Pattern.compile(Pattern.quote(txt), ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0); + } + int pos = 0; + for (Entry item : asms.entrySet()) { + pos++; + String workText = AppStrings.translate("work.searching"); + String decAdd = ""; + ASMSource asm = item.getValue(); + if (!SWF.isCached(asm)) { + decAdd = ", " + AppStrings.translate("work.decompiling"); + } + + Main.startWork(workText + " \"" + txt + "\"" + decAdd + " - (" + pos + "/" + asms.size() + ") " + item.getKey() + "... ", worker); + try { + if (pat.matcher(SWF.getCached(asm, null).text).find()) { + found.add(new ActionSearchResult(asm, item.getKey())); + } + } catch (InterruptedException ex) { + break; + } + } + + return found; + } + + return null; + } + + private void setDecompiledText(final String scriptName, final String text) { + View.execInEventDispatch(() -> { + ignoreCarret = true; + decompiledEditor.setScriptName(scriptName); + decompiledEditor.setText(text); + ignoreCarret = false; + }); + } + + private void setEditorText(final String scriptName, final String text, final String contentType) { + View.execInEventDispatch(() -> { + ignoreCarret = true; + editor.setScriptName("#PCODE " + scriptName); + editor.changeContentType(contentType); + editor.setText(text); + ignoreCarret = false; + }); + } + + private void setText(final HighlightedText text, final String contentType, final String scriptName) { + View.execInEventDispatch(() -> { + int pos = editor.getCaretPosition(); + Highlighting lastH = null; + for (Highlighting h : disassembledHilights) { + if (pos < h.startPos) { + break; + } + lastH = h; + } + Long offset = lastH == null ? 0 : lastH.getProperties().offset; + disassembledHilights = text.instructionHilights; + String stripped = text.text; + setEditorText(scriptName, stripped, contentType); + Highlighting h = Highlighting.searchOffset(disassembledHilights, offset); + if (h != null) { + if (h.startPos <= editor.getDocument().getLength()) { + editor.setCaretPosition(h.startPos); + } + } + }); + } + + private HighlightedText getHighlightedText(ScriptExportMode exportMode) { + ASMSource asm = (ASMSource) src; + DisassemblyListener listener = getDisassemblyListener(); + asm.addDisassemblyListener(listener); + HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); + try { + asm.getASMSource(exportMode, writer, lastCode); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + asm.removeDisassemblyListener(listener); + return new HighlightedText(writer); + } + + public void setHex(ScriptExportMode exportMode, String scriptName) { + switch (exportMode) { + case PCODE: + if (srcNoHex == null) { + srcNoHex = getHighlightedText(exportMode); + } + + setText(srcNoHex, "text/flasm", scriptName); + break; + case PCODE_HEX: + if (srcWithHex == null) { + srcWithHex = getHighlightedText(exportMode); + } + + setText(srcWithHex, "text/flasm", scriptName); + break; + case HEX: + if (srcHexOnly == null) { + HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); + Helper.byteArrayToHexWithHeader(writer, src.getActionBytes().getRangeData()); + srcHexOnly = new HighlightedText(writer); + } + + setText(srcHexOnly, "text/plain", scriptName); + break; + case CONSTANTS: + if (srcConstants == null) { + srcConstants = getHighlightedText(exportMode); + } + + setText(srcConstants, "text/plain", scriptName); + break; + default: + throw new Error("Export mode not supported: " + exportMode); + } + } + + private DisassemblyListener getDisassemblyListener() { + DisassemblyListener listener = new DisassemblyListener() { + int percent = 0; + + String phase = ""; + + private void progress(String phase, long pos, long total) { + if (total < 1) { + return; + } + int newpercent = (int) (pos * 100 / total); + if (((newpercent > percent) || (!this.phase.equals(phase))) && newpercent <= 100) { + percent = newpercent; + this.phase = phase; + // todo: honfika: it is very slow to show every percent + setEditorText("-", "; " + AppStrings.translate("work.disassembling") + " - " + phase + " " + percent + "%...", "text/flasm"); + } + } + + @Override + public void progressReading(long pos, long total) { + progress(AppStrings.translate("disassemblingProgress.reading"), pos, total); + } + + @Override + public void progressToString(long pos, long total) { + progress(AppStrings.translate("disassemblingProgress.toString"), pos, total); + } + + @Override + public void progressDeobfuscating(long pos, long total) { + progress(AppStrings.translate("disassemblingProgress.deobfuscating"), pos, total); + } + }; + return listener; + } + + public void setSource(final ASMSource src, final boolean useCache) { + if (setSourceWorker != null) { + setSourceWorker.cancel(true); + setSourceWorker = null; + } + + clearSource(); + this.src = src; + final ASMSource asm = (ASMSource) src; + + if (!useCache) { + SWF.uncache(asm); + } + + boolean decompile = Configuration.decompile.get(); + if (!decompile) { + lastDecompiled = Helper.getDecompilationSkippedComment(); + setDecompiledText(asm.getScriptName(), lastDecompiled); + } else { + CachedScript sc = SWF.getFromCache(asm); + if (sc != null) { + decompile = false; + decompiledHilights = sc.hilights; + methodHighlights = sc.methodHilights; + classHighlights = sc.classHilights; + specialHighlights = sc.specialHilights; + lastDecompiled = sc.text; + lastASM = asm; + setDecompiledText(lastASM.getScriptName(), lastDecompiled); + } + } + + if (!decompile) { + setDecompiledEditMode(false); + } + + final boolean decompileNeeded = decompile; + + CancellableWorker worker = new CancellableWorker() { + + @Override + protected Void doInBackground() throws Exception { + + setEditorText(asm.getScriptName(), "; " + AppStrings.translate("work.disassembling") + "...", "text/flasm"); + if (decompileNeeded) { + setDecompiledText("-", "// " + AppStrings.translate("work.waitingfordissasembly") + "..."); + } + DisassemblyListener listener = getDisassemblyListener(); + asm.addDisassemblyListener(listener); + ActionList actions = asm.getActions(); + lastCode = actions; + asm.removeDisassemblyListener(listener); + setHex(getExportMode(), asm.getScriptName()); + if (decompileNeeded) { + setDecompiledText("-", "// " + AppStrings.translate("work.decompiling") + "..."); + + CachedScript sc = SWF.getCached(asm, actions); + decompiledHilights = sc.hilights; + methodHighlights = sc.methodHilights; + classHighlights = sc.classHilights; + specialHighlights = sc.specialHilights; + lastDecompiled = sc.text; + lastASM = asm; + setDecompiledText(lastASM.getScriptName(), lastDecompiled); + setDecompiledEditMode(false); + } + setEditMode(false); + + return null; + } + + @Override + protected void done() { + View.execInEventDispatch(() -> { + setSourceWorker = null; + if (!Main.isDebugging()) { + Main.stopWork(); + } + + try { + get(); + } catch (CancellationException ex) { + setEditorText("-", "; " + AppStrings.translate("work.canceled"), "text/flasm"); + } catch (Exception ex) { + setDecompiledText("-", "// " + AppStrings.translate("decompilationError") + ": " + ex); + } + }); + } + }; + worker.execute(); + setSourceWorker = worker; + if (!Main.isDebugging()) { + Main.startWork(AppStrings.translate("work.decompiling") + "...", worker); + } + } + + public void hilightOffset(long offset) { + } + + public int getLocalDeclarationOfPos(int pos) { + Highlighting sh = Highlighting.searchPos(specialHighlights, pos); + Highlighting h = Highlighting.searchPos(decompiledHilights, pos); + + if (h == null) { + return -1; + } + + List tms = Highlighting.searchAllPos(methodHighlights, pos); + if (tms.isEmpty()) { + return -1; + } + for (Highlighting tm : tms) { + + List tm_tms = Highlighting.searchAllLocalNames(methodHighlights, tm.getProperties().localName); + //is it already declaration? + if (h.getProperties().declaration || (sh != null && sh.getProperties().declaration)) { + return -1; //no jump + } + + String lname = h.getProperties().localName; + if ("this".equals(lname)) { + Highlighting ch = Highlighting.searchPos(classHighlights, pos); + // int cindex = (int) ch.getProperties().index; + return ch.startPos; + } + + HighlightData hData = h.getProperties(); + HighlightData search = new HighlightData(); + search.declaration = hData.declaration; + //search.declaredType = hData.declaredType; + search.localName = hData.localName; + //search.specialValue = hData.specialValue; + if (search.isEmpty()) { + return -1; + } + search.declaration = true; + + for (Highlighting tm1 : tm_tms) { + Highlighting rh = Highlighting.search(decompiledHilights, search, tm1.startPos, tm1.startPos + tm1.len); + if (rh != null) { + return rh.startPos; + } + } + } + + return -1; + } + + public ActionPanel(MainPanel mainPanel) { + this.mainPanel = mainPanel; + editor = new DebuggableEditorPane(); + editor.setEditable(false); + decompiledEditor = new DebuggableEditorPane(); + decompiledEditor.setEditable(false); + decompiledEditor.setLinkHandler(new LinkHandler() { + + @Override + public boolean isLink(Token token) { + int pos = token.start; + Highlighting h = Highlighting.searchPos(decompiledHilights, pos); + if (h != null) { + if (h.getProperties().localName != null && !h.getProperties().declaration) { + return getLocalDeclarationOfPos(pos) != -1; + } + } + return false; + } + + @Override + public void handleLink(Token token) { + int pos = token.start; + int tpos = getLocalDeclarationOfPos(pos); + if (tpos > -1) { + //System.err.println("goto " + tpos); + decompiledEditor.setCaretPosition(tpos); + } else { + //System.err.println("cannot handle"); + } + } + + @Override + public Highlighter.HighlightPainter linkPainter() { + return decompiledEditor.linkPainter(); + } + }); + + searchPanel = new SearchPanel<>(new FlowLayout(), this); + + JButton graphButton = new JButton(View.getIcon("graph16")); + graphButton.addActionListener(this::graphButtonActionPerformed); + graphButton.setToolTipText(AppStrings.translate("button.viewgraph")); + graphButton.setMargin(new Insets(3, 3, 3, 3)); + + hexButton = new JToggleButton(View.getIcon("hexas16")); + hexButton.addActionListener(this::hexButtonActionPerformed); + hexButton.setToolTipText(AppStrings.translate("button.viewhexpcode")); + hexButton.setMargin(new Insets(3, 3, 3, 3)); + + hexOnlyButton = new JToggleButton(View.getIcon("hex16")); + hexOnlyButton.addActionListener(this::hexOnlyButtonActionPerformed); + hexOnlyButton.setToolTipText(AppStrings.translate("button.viewhex")); + hexOnlyButton.setMargin(new Insets(3, 3, 3, 3)); + + constantsViewButton = new JToggleButton(View.getIcon("constantpool16")); + constantsViewButton.addActionListener(this::constantsViewButtonActionPerformed); + constantsViewButton.setToolTipText(AppStrings.translate("button.viewConstants")); + constantsViewButton.setMargin(new Insets(3, 3, 3, 3)); + + NoneSelectedButtonGroup exportModeButtonGroup = new NoneSelectedButtonGroup(); + exportModeButtonGroup.add(hexButton); + exportModeButtonGroup.add(hexOnlyButton); + exportModeButtonGroup.add(constantsViewButton); + + resolveConstantsButton = new JToggleButton(View.getIcon("resolveconst16")); + resolveConstantsButton.addActionListener(this::resolveConstantsButtonActionPerformed); + resolveConstantsButton.setToolTipText(AppStrings.translate("button.resolveConstants")); + resolveConstantsButton.setMargin(new Insets(3, 3, 3, 3)); + resolveConstantsButton.setSelected(Configuration.resolveConstants.get()); + + showFileOffsetInPcodeHexButton = new JToggleButton(View.getIcon("fileoffset16")); + showFileOffsetInPcodeHexButton.addActionListener(this::showFileOffsetInPcodeHexButtonActionPerformed); + showFileOffsetInPcodeHexButton.setToolTipText(AppStrings.translate("button.showFileOffsetInPcodeHex")); + showFileOffsetInPcodeHexButton.setMargin(new Insets(3, 3, 3, 3)); + showFileOffsetInPcodeHexButton.setSelected(Configuration.showFileOffsetInPcodeHex.get()); + + showOriginalBytesInPcodeHexButton = new JToggleButton(View.getIcon("originalbytes16")); + showOriginalBytesInPcodeHexButton.addActionListener(this::showOriginalBytesInPcodeHexButtonActionPerformed); + showOriginalBytesInPcodeHexButton.setToolTipText(AppStrings.translate("button.showOriginalBytesInPcodeHex")); + showOriginalBytesInPcodeHexButton.setMargin(new Insets(3, 3, 3, 3)); + showOriginalBytesInPcodeHexButton.setSelected(Configuration.showOriginalBytesInPcodeHex.get()); + + topButtonsPan = new JPanel(); + topButtonsPan.setLayout(new BoxLayout(topButtonsPan, BoxLayout.X_AXIS)); + topButtonsPan.add(graphButton); + topButtonsPan.add(Box.createRigidArea(new Dimension(10, 0))); + topButtonsPan.add(hexButton); + topButtonsPan.add(hexOnlyButton); + topButtonsPan.add(constantsViewButton); + topButtonsPan.add(Box.createRigidArea(new Dimension(10, 0))); + topButtonsPan.add(resolveConstantsButton); + topButtonsPan.add(showFileOffsetInPcodeHexButton); + topButtonsPan.add(showOriginalBytesInPcodeHexButton); + + JPanel panCode = new JPanel(new BorderLayout()); + panCode.add(new JScrollPane(editor), BorderLayout.CENTER); + panCode.add(topButtonsPan, BorderLayout.NORTH); + + JPanel panB = new JPanel(); + panB.setLayout(new BorderLayout()); + asmLabel.setHorizontalAlignment(SwingConstants.CENTER); + //asmLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); + panB.add(asmLabel, BorderLayout.NORTH); + panB.add(panCode, BorderLayout.CENTER); + + JPanel buttonsPan = new JPanel(); + buttonsPan.setLayout(new FlowLayout()); + buttonsPan.add(editButton); + buttonsPan.add(saveButton); + buttonsPan.add(cancelButton); + + editButton.setMargin(new Insets(3, 3, 3, 10)); + saveButton.setMargin(new Insets(3, 3, 3, 10)); + cancelButton.setMargin(new Insets(3, 3, 3, 10)); + + JPanel decButtonsPan = new JPanel(new FlowLayout()); + decButtonsPan.add(editDecompiledButton); + decButtonsPan.add(experimentalLabel); + decButtonsPan.add(saveDecompiledButton); + decButtonsPan.add(cancelDecompiledButton); + + editDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); + saveDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); + cancelDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); + + //buttonsPan.add(saveHexButton); + //buttonsPan.add(loadHexButton); + panB.add(buttonsPan, BorderLayout.SOUTH); + + saveButton.addActionListener(this::saveActionButtonActionPerformed); + editButton.addActionListener(this::editActionButtonActionPerformed); + cancelButton.addActionListener(this::cancelActionButtonActionPerformed); + saveButton.setVisible(false); + cancelButton.setVisible(false); + + saveDecompiledButton.addActionListener(this::saveDecompiledButtonActionPerformed); + editDecompiledButton.addActionListener(this::editDecompiledButtonActionPerformed); + cancelDecompiledButton.addActionListener(this::cancelDecompiledButtonActionPerformed); + saveDecompiledButton.setVisible(false); + cancelDecompiledButton.setVisible(false); + + JPanel panA = new JPanel(new BorderLayout()); + + panA.add(decLabel, BorderLayout.NORTH); + + DebugPanel debugPanel = new DebugPanel(); + + panA.add(new JPersistentSplitPane(JSplitPane.VERTICAL_SPLIT, new JScrollPane(decompiledEditor), debugPanel, Configuration.guiActionVarsSplitPaneDividerLocationPercent), BorderLayout.CENTER); + panA.add(decButtonsPan, BorderLayout.SOUTH); + + //decPanel.add(searchPanel, BorderLayout.NORTH); + Main.getDebugHandler().addConnectionListener(new DebuggerHandler.ConnectionListener() { + + @Override + public void connected() { + decButtonsPan.setVisible(false); + } + + @Override + public void disconnected() { + decButtonsPan.setVisible(true); + } + }); + + //new JSplitPane(JSplitPane.VERTICAL_SPLIT, decompiledEditor, debugPanel) + //decPanel.add(decButtonsPan, BorderLayout.SOUTH); + //JPanel panBot = new JPanel(new BorderLayout()); + //panBot.add(decButtonsPan, BorderLayout.NORTH); + //panBot.add(debugPanel, BorderLayout.CENTER); + //panA.add(decButtonsPan, BorderLayout.SOUTH); + debugPanel.setVisible(false); + + decLabel.setHorizontalAlignment(SwingConstants.CENTER); + //decLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); + + setLayout(new BorderLayout()); + add(splitPane = new JPersistentSplitPane(JSplitPane.HORIZONTAL_SPLIT, panA, panB, Configuration.guiActionSplitPaneDividerLocationPercent), BorderLayout.CENTER); + + editor.setFont(new Font("Monospaced", Font.PLAIN, editor.getFont().getSize())); + decompiledEditor.setFont(new Font("Monospaced", Font.PLAIN, decompiledEditor.getFont().getSize())); + decompiledEditor.changeContentType("text/actionscript"); + + editor.addCaretListener(new CaretListener() { + @Override + public void caretUpdate(CaretEvent e) { + if (ignoreCarret) { + return; + } + if (editMode || editDecompiledMode) { + return; + } + editor.getCaret().setVisible(true); + int pos = editor.getCaretPosition(); + Highlighting lastH = null; + for (Highlighting h : disassembledHilights) { + if (pos < h.startPos) { + break; + } + lastH = h; + } + Long ofs = lastH == null ? 0 : lastH.getProperties().offset; + Highlighting h2 = Highlighting.searchOffset(decompiledHilights, ofs); + if (h2 != null) { + ignoreCarret = true; + if (h2.startPos <= decompiledEditor.getDocument().getLength()) { + decompiledEditor.setCaretPosition(h2.startPos); + } + decompiledEditor.getCaret().setVisible(true); + ignoreCarret = false; + + } + } + }); + + decompiledEditor.addCaretListener(new CaretListener() { + @Override + public void caretUpdate(CaretEvent e) { + if (ignoreCarret) { + return; + } + if (editMode || editDecompiledMode) { + return; + } + decompiledEditor.getCaret().setVisible(true); + int pos = decompiledEditor.getCaretPosition(); + Highlighting h = Highlighting.searchPos(decompiledHilights, pos); + if (h != null) { + Highlighting h2 = Highlighting.searchOffset(disassembledHilights, h.getProperties().offset); + if (h2 != null) { + ignoreCarret = true; + if (h2.startPos > 0 && h2.startPos < editor.getText().length()) { + editor.setCaretPosition(h2.startPos); + } + editor.getCaret().setVisible(true); + ignoreCarret = false; + } + } + } + }); + + editor.addTextChangedListener(this::editorTextChanged); + decompiledEditor.addTextChangedListener(this::decompiledEditorTextChanged); + } + + private void editorTextChanged() { + setModified(true); + } + + private void decompiledEditorTextChanged() { + setDecompiledModified(true); + } + + private boolean isModified() { + return saveButton.isVisible() && saveButton.isEnabled(); + } + + private void setModified(boolean value) { + saveButton.setEnabled(value); + cancelButton.setEnabled(value); + } + + private boolean isDecompiledModified() { + return saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled(); + } + + private void setDecompiledModified(boolean value) { + saveDecompiledButton.setEnabled(value); + cancelDecompiledButton.setEnabled(value); + } + + public void setEditMode(boolean val) { + View.execInEventDispatch(() -> { + if (val) { + if (hexOnlyButton.isSelected()) { + setHex(ScriptExportMode.HEX, src.getScriptName()); + } else if (constantsViewButton.isSelected()) { + setHex(ScriptExportMode.CONSTANTS, src.getScriptName()); + } else { + setHex(ScriptExportMode.PCODE, src.getScriptName()); + } + } + + editor.setEditable(val); + saveButton.setVisible(val); + saveButton.setEnabled(false); + editButton.setVisible(!val); + cancelButton.setVisible(val); + + editor.getCaret().setVisible(true); + asmLabel.setIcon(val ? View.getIcon("editing16") : null); // this line is not working + topButtonsPan.setVisible(!val); + editMode = val; + editor.requestFocusInWindow(); + }); + } + + public void setDecompiledEditMode(boolean val) { + if (lastASM == null) { + return; + } + View.execInEventDispatch(() -> { + int lastLine = decompiledEditor.getLine(); + int prefLines = lastASM.getPrefixLineCount(); + if (val) { + String newText = lastASM.removePrefixAndSuffix(lastDecompiled); + setDecompiledText(lastASM.getScriptName(), newText); + if (lastLine > -1) { + if (lastLine - prefLines >= 0) { + decompiledEditor.gotoLine(lastLine - prefLines + 1); + } + } + } else { + String newText = lastDecompiled; + setDecompiledText(lastASM.getScriptName(), newText); + if (lastLine > -1) { + decompiledEditor.gotoLine(lastLine + prefLines + 1); + } + } + + decompiledEditor.setEditable(val); + saveDecompiledButton.setVisible(val); + saveDecompiledButton.setEnabled(false); + editDecompiledButton.setVisible(!val); + experimentalLabel.setVisible(!val); + cancelDecompiledButton.setVisible(val); + + decompiledEditor.getCaret().setVisible(true); + decLabel.setIcon(val ? View.getIcon("editing16") : null); + editDecompiledMode = val; + decompiledEditor.requestFocusInWindow(); + }); + } + + private void graphButtonActionPerformed(ActionEvent evt) { + if (lastCode != null) { + try { + GraphDialog gf = new GraphDialog(mainPanel.getMainFrame().getWindow(), new ActionGraph(lastCode, new HashMap<>(), new HashMap<>(), new HashMap<>(), SWF.DEFAULT_VERSION), ""); + gf.setVisible(true); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + } + + private void editActionButtonActionPerformed(ActionEvent evt) { + setEditMode(true); + } + + private void hexButtonActionPerformed(ActionEvent evt) { + setHex(getExportMode(), src.getScriptName()); + } + + private void hexOnlyButtonActionPerformed(ActionEvent evt) { + setHex(getExportMode(), src.getScriptName()); + } + + private void constantsViewButtonActionPerformed(ActionEvent evt) { + setHex(getExportMode(), src.getScriptName()); + } + + private void resolveConstantsButtonActionPerformed(ActionEvent evt) { + boolean resolve = resolveConstantsButton.isSelected(); + Configuration.resolveConstants.set(resolve); + + srcWithHex = null; + srcNoHex = null; + // srcHexOnly = null; is not needed since it does not contains the resolved constant names + setHex(getExportMode(), src.getScriptName()); + } + + private void showFileOffsetInPcodeHexButtonActionPerformed(ActionEvent evt) { + boolean resolve = showFileOffsetInPcodeHexButton.isSelected(); + Configuration.showFileOffsetInPcodeHex.set(resolve); + + srcWithHex = null; + setHex(getExportMode(), src.getScriptName()); + } + + private void showOriginalBytesInPcodeHexButtonActionPerformed(ActionEvent evt) { + boolean resolve = showOriginalBytesInPcodeHexButton.isSelected(); + Configuration.showOriginalBytesInPcodeHex.set(resolve); + + srcWithHex = null; + setHex(getExportMode(), src.getScriptName()); + } + + private void cancelActionButtonActionPerformed(ActionEvent evt) { + setEditMode(false); + setHex(getExportMode(), src.getScriptName()); + } + + private void saveActionButtonActionPerformed(ActionEvent evt) { + try { + String text = editor.getText(); + String trimmed = text.trim(); + if (trimmed.startsWith(Helper.hexData)) { + src.setActionBytes(Helper.getBytesFromHexaText(text)); + } else if (trimmed.startsWith(Helper.constants)) { + List> constantPools = Helper.getConstantPoolsFromText(text); + try { + Action.setConstantPools(src, constantPools, true); + } catch (ConstantPoolTooBigException ex) { + View.showMessageDialog(this, AppStrings.translate("error.constantPoolTooBig").replace("%index%", Integer.toString(ex.index)).replace("%size%", Integer.toString(ex.size)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + } else { + src.setActions(ASMParser.parse(0, true, text, src.getSwf().version, false)); + } + + SWF.uncache(src); + src.setModified(); + setSource(this.src, false); + View.showMessageDialog(this, AppStrings.translate("message.action.saved"), AppStrings.translate("dialog.message.title"), JOptionPane.INFORMATION_MESSAGE, Configuration.showCodeSavedMessage); + saveButton.setVisible(false); + cancelButton.setVisible(false); + editButton.setVisible(true); + editor.setEditable(false); + editMode = false; + mainPanel.refreshTree(src.getSwf()); + } catch (IOException ex) { + } catch (ActionParseException ex) { + editor.gotoLine((int) ex.line); + editor.markError(); + View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } catch (Throwable ex) { + logger.log(Level.SEVERE, null, ex); + } + } + + private void editDecompiledButtonActionPerformed(ActionEvent evt) { + if (View.showConfirmDialog(null, AppStrings.translate("message.confirm.experimental.function"), AppStrings.translate("message.warning"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, Configuration.warningExperimentalAS12Edit, JOptionPane.OK_OPTION) == JOptionPane.OK_OPTION) { + setDecompiledEditMode(true); + } + } + + private void cancelDecompiledButtonActionPerformed(ActionEvent evt) { + setDecompiledEditMode(false); + } + + private void saveDecompiledButtonActionPerformed(ActionEvent evt) { + try { + ActionScript2Parser par = new ActionScript2Parser(mainPanel.getCurrentSwf().version); + src.setActions(par.actionsFromString(decompiledEditor.getText())); + SWF.uncache(src); + src.setModified(); + setSource(src, false); + + View.showMessageDialog(this, AppStrings.translate("message.action.saved"), AppStrings.translate("dialog.message.title"), JOptionPane.INFORMATION_MESSAGE, Configuration.showCodeSavedMessage); + setDecompiledEditMode(false); + } catch (IOException ex) { + logger.log(Level.SEVERE, "IOException during action compiling", ex); + } catch (ActionParseException ex) { + View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } catch (CompilationException ex) { + View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } catch (Throwable ex) { + logger.log(Level.SEVERE, null, ex); + } + } + + private ScriptExportMode getExportMode() { + ScriptExportMode exportMode = hexOnlyButton.isSelected() ? ScriptExportMode.HEX + : hexButton.isSelected() ? ScriptExportMode.PCODE_HEX + : constantsViewButton.isSelected() ? ScriptExportMode.CONSTANTS + : ScriptExportMode.PCODE; + return exportMode; + } + + @Override + public void updateSearchPos(ActionSearchResult item) { + TagTreeModel ttm = (TagTreeModel) mainPanel.tagTree.getModel(); + TreePath tp = ttm.getTreePath(item.getSrc()); + mainPanel.tagTree.setSelectionPath(tp); + mainPanel.tagTree.scrollPathToVisible(tp); + decompiledEditor.setCaretPosition(0); + + View.execInEventDispatchLater(() -> { + searchPanel.showQuickFindDialog(decompiledEditor); + }); + } + + @Override + public boolean tryAutoSave() { + // todo: implement + return false; + } + + @Override + public boolean isEditing() { + return (saveButton.isVisible() && saveButton.isEnabled()) + || (saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled()); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/fileoffset16.png b/src/com/jpexs/decompiler/flash/gui/graphics/fileoffset16.png new file mode 100644 index 0000000000000000000000000000000000000000..5deb5c2bfafeff097a591b866aed3ed46107c9b6 GIT binary patch literal 554 zcmV+_0@eMAP)%0@X#qNm?PnI(5kN`Hz+g4i0WYE>s+)T8wBY<@uye zg$~Iqbm&kis3nQeZh}D(9i@X7oJs`+`ye?E>5EVEQth`K&cBcE+;a~AT5I7iwASMH z{dl&-N?Fh=u4Fx!3y9@VMqC|KthJV8GHH~WZDeUA#iz{}c^lV&edThLp5wU!@E3c& zN7}!L#D^xy9maUv+RTI%y7STub#}Jb4VpG_6*Q|~!TKvAZ&%(CYK*b>^N0_bV>S(|!^WFpNuEjH!Hq*^S-R!jrap zjQ51-c-YMGNs5^llk_ioUsozvZ^sF~m^Rx{8#n$D+4+$Y(=P7(s)FmyBA&!jq2;hu s5Xepwx!kz~T&Fiq@X3bD8-f*F0Bfwdz|%wR4FCWD07*qoM6N<$f-$J?xc~qF literal 0 HcmV?d00001 diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 4a52a29bb..2426914fd 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -720,3 +720,4 @@ export.script.singleFilePallelModeWarning = Single file script export is not sup button.showOriginalBytesInPcodeHex = Show original bytes button.remove = Remove +button.showFileOffsetInPcodeHex = Show file offset