/* * 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.GraphTextWriter; 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<String> 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<Long> refs) { } /** * Gets all addresses which are referenced from the list of actions * * @param list List of actions * @return List of addresses */ public static Set<Long> getActionsAllRefs(List<Action> list) { Set<Long> 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<Action> 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<Action> list, boolean addZero, int version) { byte[] bytes = Action.actionsToBytes(list, addZero, version); return new ByteArrayRange(bytes); } public static void setConstantPools(ASMSource src, List<List<String>> 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<String> 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<Action> list, long baseAddress) { long offset = baseAddress; for (Action a : list) { a.setAddress(offset); offset += a.getTotalActionLength(); } } private static void informListeners(List<DisassemblyListener> 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<DisassemblyListener> 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<Long> importantOffsets = getActionsAllRefs(list); /*List<ConstantPool> cps = SWFInputStream.getConstantPool(new ArrayList<DisassemblyListener>(), new ActionGraphSource(list, version, new HashMap<Integer, String>(), new HashMap<String, GraphTargetItem>(), new HashMap<String, GraphTargetItem>()), 0, version, path); if (!cps.isEmpty()) { setConstantPool(list, cps.get(cps.size() - 1)); }*/ HashMap<Long, List<GraphSourceItemContainer>> containers = new HashMap<>(); HashMap<GraphSourceItemContainer, Integer> 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<Long> 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<DisassemblyListener> 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<Long> 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<GraphTargetItem> output, HashMap<Integer, String> regNames, HashMap<String, GraphTargetItem> variables, HashMap<String, GraphTargetItem> 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<Action> actions, int ip) { /* List<Action> actions=new ArrayList<Action>(); 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<Action> 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<GraphTargetItem> actionsToTree(List<Action> 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 * @param writer * @return * @throws java.lang.InterruptedException */ public static GraphTextWriter actionsToSource(final ASMSource asm, final List<Action> actions, final String path, GraphTextWriter writer) throws InterruptedException { writer.suspendMeasure(); List<GraphTargetItem> 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<List<GraphTargetItem>>() { @Override public List<GraphTargetItem> call() throws Exception { int staticOperation = Graph.SOP_USE_STATIC; //(Boolean) Configuration.getConfig("autoDeobfuscate", true) ? Graph.SOP_SKIP_STATIC : Graph.SOP_USE_STATIC; List<GraphTargetItem> 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); } return 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<GraphTargetItem> actionsToTree(HashMap<Integer, String> regNames, HashMap<String, GraphTargetItem> variables, HashMap<String, GraphTargetItem> functions, List<Action> 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<GraphTargetItem> 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<Integer> 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<GraphTargetItem> actionsPartToTree(Reference<GraphSourceItem> fi, HashMap<Integer, String> registerNames, HashMap<String, GraphTargetItem> variables, HashMap<String, GraphTargetItem> functions, TranslateStack stack, List<Action> 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<GraphTargetItem> 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<GraphTargetItem> out=actionsPartToTree(new HashMap<Integer, String>(), new HashMap<String, GraphTargetItem>(),new HashMap<String, GraphTargetItem>(), new TranslateStack(), src, ip+1,endip-1 , version); long endAddr = action.getAddress() + cnt.getHeaderSize(); String cntName = cnt.getName(); List<List<GraphTargetItem>> outs = new ArrayList<>(); HashMap<String, GraphTargetItem> 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<GraphTargetItem> out; try { HashMap<Integer, String> regNames = cnt.getRegNames(); if (action instanceof ActionWith || action instanceof ActionTry) { for (Map.Entry<Integer, String> 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<GraphTargetItem> 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<GraphTargetItem> catchExceptions = new ArrayList<GraphTargetItem>(); catchExceptions.add(catchName); List<List<GraphTargetItem>> catchCommands = new ArrayList<List<GraphTargetItem>>(); catchCommands.add(ActionGraph.translateViaGraph(registerNames, variables, functions, atry.catchBody, version)); List<GraphTargetItem> 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<GraphTargetItem> withCommands = ActionGraph.translateViaGraph(registerNames, variables, functions,new ArrayList<Action>() , 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<ActionItem> caseValues = new ArrayList<ActionItem>(); List<List<ActionItem>> caseCommands = new ArrayList<List<ActionItem>>(); 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<Integer> caseBodyIps = new ArrayList<Integer>(); 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<GraphTargetItem> checkClass(List<GraphTargetItem> output) { if (true) { //return output; } List<GraphTargetItem> ret = new ArrayList<>(); List<GraphTargetItem> functions = new ArrayList<>(); List<GraphTargetItem> staticFunctions = new ArrayList<>(); List<MyEntry<GraphTargetItem, GraphTargetItem>> vars = new ArrayList<>(); List<MyEntry<GraphTargetItem, GraphTargetItem>> staticVars = new ArrayList<>(); GraphTargetItem className; GraphTargetItem extendsOp = null; List<GraphTargetItem> 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<GraphTargetItem> 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<GraphTargetItem> 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<GraphTargetItem> 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<? extends GraphSourceItem> 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<Long> 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<VariableActionItem> 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; } }